diff --git a/annotation-processor/build.gradle b/annotation-processor/build.gradle
new file mode 100644
index 000000000..4518d0e5b
--- /dev/null
+++ b/annotation-processor/build.gradle
@@ -0,0 +1,14 @@
+apply plugin: "java-library"
+
+java {
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+}
+dependencies {
+
+ implementation project(':annotation')
+
+ annotationProcessor 'com.google.auto.service:auto-service:1.0.1'
+ compileOnly 'com.google.auto.service:auto-service:1.0.1'
+
+}
\ No newline at end of file
diff --git a/libs/annotation-processor/src/main/java/im/conversations/android/annotation/processor/XmlElementProcessor.java b/annotation-processor/src/main/java/im/conversations/android/annotation/processor/XmlElementProcessor.java
similarity index 100%
rename from libs/annotation-processor/src/main/java/im/conversations/android/annotation/processor/XmlElementProcessor.java
rename to annotation-processor/src/main/java/im/conversations/android/annotation/processor/XmlElementProcessor.java
diff --git a/annotation/build.gradle b/annotation/build.gradle
new file mode 100644
index 000000000..d5917e460
--- /dev/null
+++ b/annotation/build.gradle
@@ -0,0 +1,6 @@
+apply plugin: "java-library"
+
+java {
+ sourceCompatibility = JavaVersion.VERSION_11
+ targetCompatibility = JavaVersion.VERSION_11
+}
\ No newline at end of file
diff --git a/libs/annotation/src/main/java/im/conversations/android/annotation/XmlElement.java b/annotation/src/main/java/im/conversations/android/annotation/XmlElement.java
similarity index 100%
rename from libs/annotation/src/main/java/im/conversations/android/annotation/XmlElement.java
rename to annotation/src/main/java/im/conversations/android/annotation/XmlElement.java
diff --git a/libs/annotation/src/main/java/im/conversations/android/annotation/XmlPackage.java b/annotation/src/main/java/im/conversations/android/annotation/XmlPackage.java
similarity index 100%
rename from libs/annotation/src/main/java/im/conversations/android/annotation/XmlPackage.java
rename to annotation/src/main/java/im/conversations/android/annotation/XmlPackage.java
diff --git a/app/build.gradle b/app/build.gradle
new file mode 100644
index 000000000..7711816a5
--- /dev/null
+++ b/app/build.gradle
@@ -0,0 +1,127 @@
+apply plugin: "com.android.application"
+apply plugin: "androidx.navigation.safeargs"
+apply plugin: "com.diffplug.spotless"
+
+
+android {
+ namespace 'im.conversations.android'
+ compileSdk 33
+
+ defaultConfig {
+ minSdk 23
+ targetSdk 33
+ versionCode 1
+ versionName "3.0.0-alpha"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+
+ javaCompileOptions {
+ annotationProcessorOptions {
+ arguments += ["room.schemaLocation": "$projectDir/schemas".toString()]
+ }
+ }
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+ compileOptions {
+ coreLibraryDesugaringEnabled true
+ sourceCompatibility JavaVersion.VERSION_11
+ targetCompatibility JavaVersion.VERSION_11
+ }
+ buildFeatures {
+ dataBinding true
+ }
+ flavorDimensions "product"
+ productFlavors {
+ quicksy {
+ dimension "product"
+ applicationId = "im.quicksy.client"
+
+ def appName = "Quicksy"
+
+ resValue "string", "applicationId", applicationId
+ resValue "string", "app_name", appName
+ buildConfigField "String", "APP_NAME", "\"$appName\""
+ }
+ conversations {
+ dimension "product"
+ applicationId "im.conversations.android"
+
+ def appName = "Conversations"
+
+ resValue "string", "applicationId", applicationId
+ resValue "string", "app_name", appName
+ buildConfigField "String", "APP_NAME", "\"$appName\""
+ }
+ }
+
+}
+
+spotless {
+ java {
+ target '**/*.java'
+ googleJavaFormat('1.8').aosp().reflowLongStrings()
+ }
+}
+
+dependencies {
+ implementation project(':annotation')
+ annotationProcessor project(':annotation-processor')
+
+ // make Java 8 API available
+ coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.2'
+
+
+ // Jetpack / AndroidX libraries
+ implementation "androidx.appcompat:appcompat:$rootProject.ext.appcompatVersion"
+
+ implementation "androidx.lifecycle:lifecycle-extensions:$rootProject.ext.lifecycleVersion"
+ implementation "androidx.navigation:navigation-fragment:$rootProject.ext.navVersion"
+ implementation "androidx.navigation:navigation-ui:$rootProject.ext.navVersion"
+
+ implementation "androidx.room:room-runtime:$rootProject.ext.roomVersion"
+ implementation "androidx.room:room-guava:$rootProject.ext.roomVersion"
+ annotationProcessor "androidx.room:room-compiler:$rootProject.ext.roomVersion"
+
+ implementation "androidx.security:security-crypto:1.0.0"
+
+
+ // Google material design libraries
+ implementation "com.google.android.material:material:$rootProject.ext.material"
+
+
+ // crypto libraries
+ implementation 'org.whispersystems:signal-protocol-java:2.6.2'
+ implementation 'org.conscrypt:conscrypt-android:2.5.2'
+ implementation 'org.bouncycastle:bcmail-jdk15on:1.64'
+
+
+ // XMPP Address library
+ implementation 'org.jxmpp:jxmpp-jid:1.0.3'
+
+ // DNS library (XMPP needs to resolve SRV records)
+ implementation 'de.measite.minidns:minidns-hla:0.2.4'
+
+ // Guava
+ implementation 'com.google.guava:guava:31.1-android'
+
+ // HTTP library
+ implementation "com.squareup.okhttp3:okhttp:4.10.0"
+
+ // JSON parser
+ implementation 'com.google.code.gson:gson:2.10.1'
+
+ // logging framework + logging api
+ implementation 'org.slf4j:slf4j-api:1.7.36'
+ implementation 'com.github.tony19:logback-android:2.0.1'
+
+ testImplementation 'junit:junit:4.13.2'
+ testImplementation 'org.robolectric:robolectric:4.9.2'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.5'
+ androidTestImplementation "androidx.test.espresso:espresso-core:$rootProject.ext.espressoVersion"
+}
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
new file mode 100644
index 000000000..481bb4348
--- /dev/null
+++ b/app/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
\ No newline at end of file
diff --git a/schemas/im.conversations.android.database.ConversationsDatabase/1.json b/app/schemas/im.conversations.android.database.ConversationsDatabase/1.json
similarity index 100%
rename from schemas/im.conversations.android.database.ConversationsDatabase/1.json
rename to app/schemas/im.conversations.android.database.ConversationsDatabase/1.json
diff --git a/src/androidTest/java/im/conversations/android/xmpp/TransformationTest.java b/app/src/androidTest/java/im/conversations/android/xmpp/TransformationTest.java
similarity index 100%
rename from src/androidTest/java/im/conversations/android/xmpp/TransformationTest.java
rename to app/src/androidTest/java/im/conversations/android/xmpp/TransformationTest.java
diff --git a/src/conversations/res/drawable/ic_launcher_foreground.xml b/app/src/conversations/res/drawable/ic_launcher_foreground.xml
similarity index 100%
rename from src/conversations/res/drawable/ic_launcher_foreground.xml
rename to app/src/conversations/res/drawable/ic_launcher_foreground.xml
diff --git a/src/conversations/res/mipmap-anydpi-v26/new_launcher.xml b/app/src/conversations/res/mipmap-anydpi-v26/new_launcher.xml
similarity index 100%
rename from src/conversations/res/mipmap-anydpi-v26/new_launcher.xml
rename to app/src/conversations/res/mipmap-anydpi-v26/new_launcher.xml
diff --git a/src/conversations/res/mipmap-anydpi-v26/new_launcher_round.xml b/app/src/conversations/res/mipmap-anydpi-v26/new_launcher_round.xml
similarity index 100%
rename from src/conversations/res/mipmap-anydpi-v26/new_launcher_round.xml
rename to app/src/conversations/res/mipmap-anydpi-v26/new_launcher_round.xml
diff --git a/src/main/res/mipmap-hdpi/ic_launcher_background.png b/app/src/conversations/res/mipmap-hdpi/ic_launcher_background.png
similarity index 100%
rename from src/main/res/mipmap-hdpi/ic_launcher_background.png
rename to app/src/conversations/res/mipmap-hdpi/ic_launcher_background.png
diff --git a/src/conversations/res/mipmap-hdpi/new_launcher.png b/app/src/conversations/res/mipmap-hdpi/new_launcher.png
similarity index 100%
rename from src/conversations/res/mipmap-hdpi/new_launcher.png
rename to app/src/conversations/res/mipmap-hdpi/new_launcher.png
diff --git a/src/conversations/res/mipmap-hdpi/new_launcher_round.png b/app/src/conversations/res/mipmap-hdpi/new_launcher_round.png
similarity index 100%
rename from src/conversations/res/mipmap-hdpi/new_launcher_round.png
rename to app/src/conversations/res/mipmap-hdpi/new_launcher_round.png
diff --git a/src/main/res/mipmap-mdpi/ic_launcher_background.png b/app/src/conversations/res/mipmap-mdpi/ic_launcher_background.png
similarity index 100%
rename from src/main/res/mipmap-mdpi/ic_launcher_background.png
rename to app/src/conversations/res/mipmap-mdpi/ic_launcher_background.png
diff --git a/src/conversations/res/mipmap-mdpi/new_launcher.png b/app/src/conversations/res/mipmap-mdpi/new_launcher.png
similarity index 100%
rename from src/conversations/res/mipmap-mdpi/new_launcher.png
rename to app/src/conversations/res/mipmap-mdpi/new_launcher.png
diff --git a/src/conversations/res/mipmap-mdpi/new_launcher_round.png b/app/src/conversations/res/mipmap-mdpi/new_launcher_round.png
similarity index 100%
rename from src/conversations/res/mipmap-mdpi/new_launcher_round.png
rename to app/src/conversations/res/mipmap-mdpi/new_launcher_round.png
diff --git a/src/main/res/mipmap-xhdpi/ic_launcher_background.png b/app/src/conversations/res/mipmap-xhdpi/ic_launcher_background.png
similarity index 100%
rename from src/main/res/mipmap-xhdpi/ic_launcher_background.png
rename to app/src/conversations/res/mipmap-xhdpi/ic_launcher_background.png
diff --git a/src/conversations/res/mipmap-xhdpi/new_launcher.png b/app/src/conversations/res/mipmap-xhdpi/new_launcher.png
similarity index 100%
rename from src/conversations/res/mipmap-xhdpi/new_launcher.png
rename to app/src/conversations/res/mipmap-xhdpi/new_launcher.png
diff --git a/src/conversations/res/mipmap-xhdpi/new_launcher_round.png b/app/src/conversations/res/mipmap-xhdpi/new_launcher_round.png
similarity index 100%
rename from src/conversations/res/mipmap-xhdpi/new_launcher_round.png
rename to app/src/conversations/res/mipmap-xhdpi/new_launcher_round.png
diff --git a/src/main/res/mipmap-xxhdpi/ic_launcher_background.png b/app/src/conversations/res/mipmap-xxhdpi/ic_launcher_background.png
similarity index 100%
rename from src/main/res/mipmap-xxhdpi/ic_launcher_background.png
rename to app/src/conversations/res/mipmap-xxhdpi/ic_launcher_background.png
diff --git a/src/conversations/res/mipmap-xxhdpi/new_launcher.png b/app/src/conversations/res/mipmap-xxhdpi/new_launcher.png
similarity index 100%
rename from src/conversations/res/mipmap-xxhdpi/new_launcher.png
rename to app/src/conversations/res/mipmap-xxhdpi/new_launcher.png
diff --git a/src/conversations/res/mipmap-xxhdpi/new_launcher_round.png b/app/src/conversations/res/mipmap-xxhdpi/new_launcher_round.png
similarity index 100%
rename from src/conversations/res/mipmap-xxhdpi/new_launcher_round.png
rename to app/src/conversations/res/mipmap-xxhdpi/new_launcher_round.png
diff --git a/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png b/app/src/conversations/res/mipmap-xxxhdpi/ic_launcher_background.png
similarity index 100%
rename from src/main/res/mipmap-xxxhdpi/ic_launcher_background.png
rename to app/src/conversations/res/mipmap-xxxhdpi/ic_launcher_background.png
diff --git a/src/conversations/res/mipmap-xxxhdpi/new_launcher.png b/app/src/conversations/res/mipmap-xxxhdpi/new_launcher.png
similarity index 100%
rename from src/conversations/res/mipmap-xxxhdpi/new_launcher.png
rename to app/src/conversations/res/mipmap-xxxhdpi/new_launcher.png
diff --git a/src/conversations/res/mipmap-xxxhdpi/new_launcher_round.png b/app/src/conversations/res/mipmap-xxxhdpi/new_launcher_round.png
similarity index 100%
rename from src/conversations/res/mipmap-xxxhdpi/new_launcher_round.png
rename to app/src/conversations/res/mipmap-xxxhdpi/new_launcher_round.png
diff --git a/src/conversations/res/values-ar/strings.xml b/app/src/conversations/res/values-ar/strings.xml
similarity index 100%
rename from src/conversations/res/values-ar/strings.xml
rename to app/src/conversations/res/values-ar/strings.xml
diff --git a/src/conversations/res/values-bg/strings.xml b/app/src/conversations/res/values-bg/strings.xml
similarity index 100%
rename from src/conversations/res/values-bg/strings.xml
rename to app/src/conversations/res/values-bg/strings.xml
diff --git a/src/conversations/res/values-bn-rIN/strings.xml b/app/src/conversations/res/values-bn-rIN/strings.xml
similarity index 100%
rename from src/conversations/res/values-bn-rIN/strings.xml
rename to app/src/conversations/res/values-bn-rIN/strings.xml
diff --git a/src/conversations/res/values-ca/strings.xml b/app/src/conversations/res/values-ca/strings.xml
similarity index 100%
rename from src/conversations/res/values-ca/strings.xml
rename to app/src/conversations/res/values-ca/strings.xml
diff --git a/src/conversations/res/values-da-rDK/strings.xml b/app/src/conversations/res/values-da-rDK/strings.xml
similarity index 100%
rename from src/conversations/res/values-da-rDK/strings.xml
rename to app/src/conversations/res/values-da-rDK/strings.xml
diff --git a/src/conversations/res/values-de/strings.xml b/app/src/conversations/res/values-de/strings.xml
similarity index 100%
rename from src/conversations/res/values-de/strings.xml
rename to app/src/conversations/res/values-de/strings.xml
diff --git a/src/conversations/res/values-el/strings.xml b/app/src/conversations/res/values-el/strings.xml
similarity index 100%
rename from src/conversations/res/values-el/strings.xml
rename to app/src/conversations/res/values-el/strings.xml
diff --git a/src/conversations/res/values-es/strings.xml b/app/src/conversations/res/values-es/strings.xml
similarity index 100%
rename from src/conversations/res/values-es/strings.xml
rename to app/src/conversations/res/values-es/strings.xml
diff --git a/src/conversations/res/values-eu/strings.xml b/app/src/conversations/res/values-eu/strings.xml
similarity index 100%
rename from src/conversations/res/values-eu/strings.xml
rename to app/src/conversations/res/values-eu/strings.xml
diff --git a/src/conversations/res/values-fa-rIR/strings.xml b/app/src/conversations/res/values-fa-rIR/strings.xml
similarity index 100%
rename from src/conversations/res/values-fa-rIR/strings.xml
rename to app/src/conversations/res/values-fa-rIR/strings.xml
diff --git a/src/conversations/res/values-fi/strings.xml b/app/src/conversations/res/values-fi/strings.xml
similarity index 100%
rename from src/conversations/res/values-fi/strings.xml
rename to app/src/conversations/res/values-fi/strings.xml
diff --git a/src/conversations/res/values-fr/strings.xml b/app/src/conversations/res/values-fr/strings.xml
similarity index 100%
rename from src/conversations/res/values-fr/strings.xml
rename to app/src/conversations/res/values-fr/strings.xml
diff --git a/src/conversations/res/values-gl/strings.xml b/app/src/conversations/res/values-gl/strings.xml
similarity index 100%
rename from src/conversations/res/values-gl/strings.xml
rename to app/src/conversations/res/values-gl/strings.xml
diff --git a/src/conversations/res/values-hr/strings.xml b/app/src/conversations/res/values-hr/strings.xml
similarity index 100%
rename from src/conversations/res/values-hr/strings.xml
rename to app/src/conversations/res/values-hr/strings.xml
diff --git a/src/conversations/res/values-hu/strings.xml b/app/src/conversations/res/values-hu/strings.xml
similarity index 100%
rename from src/conversations/res/values-hu/strings.xml
rename to app/src/conversations/res/values-hu/strings.xml
diff --git a/src/conversations/res/values-id/strings.xml b/app/src/conversations/res/values-id/strings.xml
similarity index 100%
rename from src/conversations/res/values-id/strings.xml
rename to app/src/conversations/res/values-id/strings.xml
diff --git a/src/conversations/res/values-it/strings.xml b/app/src/conversations/res/values-it/strings.xml
similarity index 100%
rename from src/conversations/res/values-it/strings.xml
rename to app/src/conversations/res/values-it/strings.xml
diff --git a/src/conversations/res/values-ja/strings.xml b/app/src/conversations/res/values-ja/strings.xml
similarity index 100%
rename from src/conversations/res/values-ja/strings.xml
rename to app/src/conversations/res/values-ja/strings.xml
diff --git a/src/conversations/res/values-nl/strings.xml b/app/src/conversations/res/values-nl/strings.xml
similarity index 100%
rename from src/conversations/res/values-nl/strings.xml
rename to app/src/conversations/res/values-nl/strings.xml
diff --git a/src/conversations/res/values-pl/strings.xml b/app/src/conversations/res/values-pl/strings.xml
similarity index 100%
rename from src/conversations/res/values-pl/strings.xml
rename to app/src/conversations/res/values-pl/strings.xml
diff --git a/src/conversations/res/values-pt-rBR/strings.xml b/app/src/conversations/res/values-pt-rBR/strings.xml
similarity index 100%
rename from src/conversations/res/values-pt-rBR/strings.xml
rename to app/src/conversations/res/values-pt-rBR/strings.xml
diff --git a/src/conversations/res/values-pt/strings.xml b/app/src/conversations/res/values-pt/strings.xml
similarity index 100%
rename from src/conversations/res/values-pt/strings.xml
rename to app/src/conversations/res/values-pt/strings.xml
diff --git a/src/conversations/res/values-ro-rRO/strings.xml b/app/src/conversations/res/values-ro-rRO/strings.xml
similarity index 100%
rename from src/conversations/res/values-ro-rRO/strings.xml
rename to app/src/conversations/res/values-ro-rRO/strings.xml
diff --git a/src/conversations/res/values-ru/strings.xml b/app/src/conversations/res/values-ru/strings.xml
similarity index 100%
rename from src/conversations/res/values-ru/strings.xml
rename to app/src/conversations/res/values-ru/strings.xml
diff --git a/src/conversations/res/values-sk/strings.xml b/app/src/conversations/res/values-sk/strings.xml
similarity index 100%
rename from src/conversations/res/values-sk/strings.xml
rename to app/src/conversations/res/values-sk/strings.xml
diff --git a/src/conversations/res/values-sq/strings.xml b/app/src/conversations/res/values-sq/strings.xml
similarity index 100%
rename from src/conversations/res/values-sq/strings.xml
rename to app/src/conversations/res/values-sq/strings.xml
diff --git a/src/conversations/res/values-sr/strings.xml b/app/src/conversations/res/values-sr/strings.xml
similarity index 100%
rename from src/conversations/res/values-sr/strings.xml
rename to app/src/conversations/res/values-sr/strings.xml
diff --git a/src/conversations/res/values-sv/strings.xml b/app/src/conversations/res/values-sv/strings.xml
similarity index 100%
rename from src/conversations/res/values-sv/strings.xml
rename to app/src/conversations/res/values-sv/strings.xml
diff --git a/src/conversations/res/values-szl/strings.xml b/app/src/conversations/res/values-szl/strings.xml
similarity index 100%
rename from src/conversations/res/values-szl/strings.xml
rename to app/src/conversations/res/values-szl/strings.xml
diff --git a/src/conversations/res/values-tr-rTR/strings.xml b/app/src/conversations/res/values-tr-rTR/strings.xml
similarity index 100%
rename from src/conversations/res/values-tr-rTR/strings.xml
rename to app/src/conversations/res/values-tr-rTR/strings.xml
diff --git a/src/conversations/res/values-uk/strings.xml b/app/src/conversations/res/values-uk/strings.xml
similarity index 100%
rename from src/conversations/res/values-uk/strings.xml
rename to app/src/conversations/res/values-uk/strings.xml
diff --git a/src/conversations/res/values-vi/strings.xml b/app/src/conversations/res/values-vi/strings.xml
similarity index 100%
rename from src/conversations/res/values-vi/strings.xml
rename to app/src/conversations/res/values-vi/strings.xml
diff --git a/src/conversations/res/values-zh-rCN/strings.xml b/app/src/conversations/res/values-zh-rCN/strings.xml
similarity index 100%
rename from src/conversations/res/values-zh-rCN/strings.xml
rename to app/src/conversations/res/values-zh-rCN/strings.xml
diff --git a/src/conversations/res/values-zh-rTW/strings.xml b/app/src/conversations/res/values-zh-rTW/strings.xml
similarity index 100%
rename from src/conversations/res/values-zh-rTW/strings.xml
rename to app/src/conversations/res/values-zh-rTW/strings.xml
diff --git a/src/conversations/res/values/strings.xml b/app/src/conversations/res/values/strings.xml
similarity index 100%
rename from src/conversations/res/values/strings.xml
rename to app/src/conversations/res/values/strings.xml
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..808baf278
--- /dev/null
+++ b/app/src/main/AndroidManifest.xml
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/assets/logback.xml b/app/src/main/assets/logback.xml
similarity index 100%
rename from src/main/assets/logback.xml
rename to app/src/main/assets/logback.xml
diff --git a/src/main/java/im/conversations/android/Conversations.java b/app/src/main/java/im/conversations/android/Conversations.java
similarity index 79%
rename from src/main/java/im/conversations/android/Conversations.java
rename to app/src/main/java/im/conversations/android/Conversations.java
index f34ffed9c..16005816d 100644
--- a/src/main/java/im/conversations/android/Conversations.java
+++ b/app/src/main/java/im/conversations/android/Conversations.java
@@ -1,7 +1,11 @@
package im.conversations.android;
import android.app.Application;
+
+import androidx.appcompat.app.AppCompatDelegate;
+
import com.google.android.material.color.DynamicColors;
+import im.conversations.android.dns.Resolver;
import im.conversations.android.xmpp.ConnectionPool;
import java.security.SecureRandom;
import java.security.Security;
@@ -23,7 +27,9 @@ public class Conversations extends Application {
} catch (final Throwable throwable) {
LOGGER.warn("Could not initialize security provider", throwable);
}
+ Resolver.init(this);
ConnectionPool.getInstance(this).reconfigure();
+ AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM); //For night mode theme
DynamicColors.applyToActivitiesIfAvailable(this);
}
}
diff --git a/src/main/java/im/conversations/android/IDs.java b/app/src/main/java/im/conversations/android/IDs.java
similarity index 100%
rename from src/main/java/im/conversations/android/IDs.java
rename to app/src/main/java/im/conversations/android/IDs.java
diff --git a/src/main/java/im/conversations/android/database/AxolotlDatabaseStore.java b/app/src/main/java/im/conversations/android/database/AxolotlDatabaseStore.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/AxolotlDatabaseStore.java
rename to app/src/main/java/im/conversations/android/database/AxolotlDatabaseStore.java
diff --git a/src/main/java/im/conversations/android/database/ConversationsDatabase.java b/app/src/main/java/im/conversations/android/database/ConversationsDatabase.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/ConversationsDatabase.java
rename to app/src/main/java/im/conversations/android/database/ConversationsDatabase.java
diff --git a/src/main/java/im/conversations/android/database/Converters.java b/app/src/main/java/im/conversations/android/database/Converters.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/Converters.java
rename to app/src/main/java/im/conversations/android/database/Converters.java
diff --git a/src/main/java/im/conversations/android/database/CredentialStore.java b/app/src/main/java/im/conversations/android/database/CredentialStore.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/CredentialStore.java
rename to app/src/main/java/im/conversations/android/database/CredentialStore.java
diff --git a/src/main/java/im/conversations/android/database/dao/AccountDao.java b/app/src/main/java/im/conversations/android/database/dao/AccountDao.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/dao/AccountDao.java
rename to app/src/main/java/im/conversations/android/database/dao/AccountDao.java
diff --git a/src/main/java/im/conversations/android/database/dao/AvatarDao.java b/app/src/main/java/im/conversations/android/database/dao/AvatarDao.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/dao/AvatarDao.java
rename to app/src/main/java/im/conversations/android/database/dao/AvatarDao.java
diff --git a/src/main/java/im/conversations/android/database/dao/AxolotlDao.java b/app/src/main/java/im/conversations/android/database/dao/AxolotlDao.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/dao/AxolotlDao.java
rename to app/src/main/java/im/conversations/android/database/dao/AxolotlDao.java
diff --git a/src/main/java/im/conversations/android/database/dao/BlockingDao.java b/app/src/main/java/im/conversations/android/database/dao/BlockingDao.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/dao/BlockingDao.java
rename to app/src/main/java/im/conversations/android/database/dao/BlockingDao.java
diff --git a/src/main/java/im/conversations/android/database/dao/BookmarkDao.java b/app/src/main/java/im/conversations/android/database/dao/BookmarkDao.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/dao/BookmarkDao.java
rename to app/src/main/java/im/conversations/android/database/dao/BookmarkDao.java
diff --git a/src/main/java/im/conversations/android/database/dao/ChatDao.java b/app/src/main/java/im/conversations/android/database/dao/ChatDao.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/dao/ChatDao.java
rename to app/src/main/java/im/conversations/android/database/dao/ChatDao.java
diff --git a/src/main/java/im/conversations/android/database/dao/DiscoDao.java b/app/src/main/java/im/conversations/android/database/dao/DiscoDao.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/dao/DiscoDao.java
rename to app/src/main/java/im/conversations/android/database/dao/DiscoDao.java
diff --git a/src/main/java/im/conversations/android/database/dao/MessageDao.java b/app/src/main/java/im/conversations/android/database/dao/MessageDao.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/dao/MessageDao.java
rename to app/src/main/java/im/conversations/android/database/dao/MessageDao.java
diff --git a/src/main/java/im/conversations/android/database/dao/NickDao.java b/app/src/main/java/im/conversations/android/database/dao/NickDao.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/dao/NickDao.java
rename to app/src/main/java/im/conversations/android/database/dao/NickDao.java
diff --git a/src/main/java/im/conversations/android/database/dao/PresenceDao.java b/app/src/main/java/im/conversations/android/database/dao/PresenceDao.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/dao/PresenceDao.java
rename to app/src/main/java/im/conversations/android/database/dao/PresenceDao.java
diff --git a/src/main/java/im/conversations/android/database/dao/RosterDao.java b/app/src/main/java/im/conversations/android/database/dao/RosterDao.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/dao/RosterDao.java
rename to app/src/main/java/im/conversations/android/database/dao/RosterDao.java
diff --git a/src/main/java/im/conversations/android/database/entity/AccountEntity.java b/app/src/main/java/im/conversations/android/database/entity/AccountEntity.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/entity/AccountEntity.java
rename to app/src/main/java/im/conversations/android/database/entity/AccountEntity.java
diff --git a/src/main/java/im/conversations/android/database/entity/AvatarAdditionalEntity.java b/app/src/main/java/im/conversations/android/database/entity/AvatarAdditionalEntity.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/entity/AvatarAdditionalEntity.java
rename to app/src/main/java/im/conversations/android/database/entity/AvatarAdditionalEntity.java
diff --git a/src/main/java/im/conversations/android/database/entity/AvatarEntity.java b/app/src/main/java/im/conversations/android/database/entity/AvatarEntity.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/entity/AvatarEntity.java
rename to app/src/main/java/im/conversations/android/database/entity/AvatarEntity.java
diff --git a/src/main/java/im/conversations/android/database/entity/AxolotlDeviceListEntity.java b/app/src/main/java/im/conversations/android/database/entity/AxolotlDeviceListEntity.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/entity/AxolotlDeviceListEntity.java
rename to app/src/main/java/im/conversations/android/database/entity/AxolotlDeviceListEntity.java
diff --git a/src/main/java/im/conversations/android/database/entity/AxolotlDeviceListItemEntity.java b/app/src/main/java/im/conversations/android/database/entity/AxolotlDeviceListItemEntity.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/entity/AxolotlDeviceListItemEntity.java
rename to app/src/main/java/im/conversations/android/database/entity/AxolotlDeviceListItemEntity.java
diff --git a/src/main/java/im/conversations/android/database/entity/AxolotlIdentityEntity.java b/app/src/main/java/im/conversations/android/database/entity/AxolotlIdentityEntity.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/entity/AxolotlIdentityEntity.java
rename to app/src/main/java/im/conversations/android/database/entity/AxolotlIdentityEntity.java
diff --git a/src/main/java/im/conversations/android/database/entity/AxolotlIdentityKeyPairEntity.java b/app/src/main/java/im/conversations/android/database/entity/AxolotlIdentityKeyPairEntity.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/entity/AxolotlIdentityKeyPairEntity.java
rename to app/src/main/java/im/conversations/android/database/entity/AxolotlIdentityKeyPairEntity.java
diff --git a/src/main/java/im/conversations/android/database/entity/AxolotlPreKeyEntity.java b/app/src/main/java/im/conversations/android/database/entity/AxolotlPreKeyEntity.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/entity/AxolotlPreKeyEntity.java
rename to app/src/main/java/im/conversations/android/database/entity/AxolotlPreKeyEntity.java
diff --git a/src/main/java/im/conversations/android/database/entity/AxolotlSessionEntity.java b/app/src/main/java/im/conversations/android/database/entity/AxolotlSessionEntity.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/entity/AxolotlSessionEntity.java
rename to app/src/main/java/im/conversations/android/database/entity/AxolotlSessionEntity.java
diff --git a/src/main/java/im/conversations/android/database/entity/AxolotlSignedPreKeyEntity.java b/app/src/main/java/im/conversations/android/database/entity/AxolotlSignedPreKeyEntity.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/entity/AxolotlSignedPreKeyEntity.java
rename to app/src/main/java/im/conversations/android/database/entity/AxolotlSignedPreKeyEntity.java
diff --git a/src/main/java/im/conversations/android/database/entity/BlockedItemEntity.java b/app/src/main/java/im/conversations/android/database/entity/BlockedItemEntity.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/entity/BlockedItemEntity.java
rename to app/src/main/java/im/conversations/android/database/entity/BlockedItemEntity.java
diff --git a/src/main/java/im/conversations/android/database/entity/BookmarkEntity.java b/app/src/main/java/im/conversations/android/database/entity/BookmarkEntity.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/entity/BookmarkEntity.java
rename to app/src/main/java/im/conversations/android/database/entity/BookmarkEntity.java
diff --git a/src/main/java/im/conversations/android/database/entity/ChatEntity.java b/app/src/main/java/im/conversations/android/database/entity/ChatEntity.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/entity/ChatEntity.java
rename to app/src/main/java/im/conversations/android/database/entity/ChatEntity.java
diff --git a/src/main/java/im/conversations/android/database/entity/DiscoEntity.java b/app/src/main/java/im/conversations/android/database/entity/DiscoEntity.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/entity/DiscoEntity.java
rename to app/src/main/java/im/conversations/android/database/entity/DiscoEntity.java
diff --git a/src/main/java/im/conversations/android/database/entity/DiscoExtensionEntity.java b/app/src/main/java/im/conversations/android/database/entity/DiscoExtensionEntity.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/entity/DiscoExtensionEntity.java
rename to app/src/main/java/im/conversations/android/database/entity/DiscoExtensionEntity.java
diff --git a/src/main/java/im/conversations/android/database/entity/DiscoExtensionFieldEntity.java b/app/src/main/java/im/conversations/android/database/entity/DiscoExtensionFieldEntity.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/entity/DiscoExtensionFieldEntity.java
rename to app/src/main/java/im/conversations/android/database/entity/DiscoExtensionFieldEntity.java
diff --git a/src/main/java/im/conversations/android/database/entity/DiscoExtensionFieldValueEntity.java b/app/src/main/java/im/conversations/android/database/entity/DiscoExtensionFieldValueEntity.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/entity/DiscoExtensionFieldValueEntity.java
rename to app/src/main/java/im/conversations/android/database/entity/DiscoExtensionFieldValueEntity.java
diff --git a/src/main/java/im/conversations/android/database/entity/DiscoFeatureEntity.java b/app/src/main/java/im/conversations/android/database/entity/DiscoFeatureEntity.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/entity/DiscoFeatureEntity.java
rename to app/src/main/java/im/conversations/android/database/entity/DiscoFeatureEntity.java
diff --git a/src/main/java/im/conversations/android/database/entity/DiscoIdentityEntity.java b/app/src/main/java/im/conversations/android/database/entity/DiscoIdentityEntity.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/entity/DiscoIdentityEntity.java
rename to app/src/main/java/im/conversations/android/database/entity/DiscoIdentityEntity.java
diff --git a/src/main/java/im/conversations/android/database/entity/DiscoItemEntity.java b/app/src/main/java/im/conversations/android/database/entity/DiscoItemEntity.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/entity/DiscoItemEntity.java
rename to app/src/main/java/im/conversations/android/database/entity/DiscoItemEntity.java
diff --git a/src/main/java/im/conversations/android/database/entity/MessageContentEntity.java b/app/src/main/java/im/conversations/android/database/entity/MessageContentEntity.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/entity/MessageContentEntity.java
rename to app/src/main/java/im/conversations/android/database/entity/MessageContentEntity.java
diff --git a/src/main/java/im/conversations/android/database/entity/MessageEntity.java b/app/src/main/java/im/conversations/android/database/entity/MessageEntity.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/entity/MessageEntity.java
rename to app/src/main/java/im/conversations/android/database/entity/MessageEntity.java
diff --git a/src/main/java/im/conversations/android/database/entity/MessageReactionEntity.java b/app/src/main/java/im/conversations/android/database/entity/MessageReactionEntity.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/entity/MessageReactionEntity.java
rename to app/src/main/java/im/conversations/android/database/entity/MessageReactionEntity.java
diff --git a/src/main/java/im/conversations/android/database/entity/MessageStateEntity.java b/app/src/main/java/im/conversations/android/database/entity/MessageStateEntity.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/entity/MessageStateEntity.java
rename to app/src/main/java/im/conversations/android/database/entity/MessageStateEntity.java
diff --git a/src/main/java/im/conversations/android/database/entity/MessageVersionEntity.java b/app/src/main/java/im/conversations/android/database/entity/MessageVersionEntity.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/entity/MessageVersionEntity.java
rename to app/src/main/java/im/conversations/android/database/entity/MessageVersionEntity.java
diff --git a/src/main/java/im/conversations/android/database/entity/NickEntity.java b/app/src/main/java/im/conversations/android/database/entity/NickEntity.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/entity/NickEntity.java
rename to app/src/main/java/im/conversations/android/database/entity/NickEntity.java
diff --git a/src/main/java/im/conversations/android/database/entity/PresenceEntity.java b/app/src/main/java/im/conversations/android/database/entity/PresenceEntity.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/entity/PresenceEntity.java
rename to app/src/main/java/im/conversations/android/database/entity/PresenceEntity.java
diff --git a/src/main/java/im/conversations/android/database/entity/RosterItemEntity.java b/app/src/main/java/im/conversations/android/database/entity/RosterItemEntity.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/entity/RosterItemEntity.java
rename to app/src/main/java/im/conversations/android/database/entity/RosterItemEntity.java
diff --git a/src/main/java/im/conversations/android/database/entity/RosterItemGroupEntity.java b/app/src/main/java/im/conversations/android/database/entity/RosterItemGroupEntity.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/entity/RosterItemGroupEntity.java
rename to app/src/main/java/im/conversations/android/database/entity/RosterItemGroupEntity.java
diff --git a/src/main/java/im/conversations/android/database/model/Account.java b/app/src/main/java/im/conversations/android/database/model/Account.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/model/Account.java
rename to app/src/main/java/im/conversations/android/database/model/Account.java
diff --git a/src/main/java/im/conversations/android/database/model/AvatarBase.java b/app/src/main/java/im/conversations/android/database/model/AvatarBase.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/model/AvatarBase.java
rename to app/src/main/java/im/conversations/android/database/model/AvatarBase.java
diff --git a/src/main/java/im/conversations/android/database/model/AvatarExternal.java b/app/src/main/java/im/conversations/android/database/model/AvatarExternal.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/model/AvatarExternal.java
rename to app/src/main/java/im/conversations/android/database/model/AvatarExternal.java
diff --git a/src/main/java/im/conversations/android/database/model/AvatarThumbnail.java b/app/src/main/java/im/conversations/android/database/model/AvatarThumbnail.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/model/AvatarThumbnail.java
rename to app/src/main/java/im/conversations/android/database/model/AvatarThumbnail.java
diff --git a/src/main/java/im/conversations/android/database/model/ChatIdentifier.java b/app/src/main/java/im/conversations/android/database/model/ChatIdentifier.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/model/ChatIdentifier.java
rename to app/src/main/java/im/conversations/android/database/model/ChatIdentifier.java
diff --git a/src/main/java/im/conversations/android/database/model/ChatType.java b/app/src/main/java/im/conversations/android/database/model/ChatType.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/model/ChatType.java
rename to app/src/main/java/im/conversations/android/database/model/ChatType.java
diff --git a/src/main/java/im/conversations/android/database/model/Connection.java b/app/src/main/java/im/conversations/android/database/model/Connection.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/model/Connection.java
rename to app/src/main/java/im/conversations/android/database/model/Connection.java
diff --git a/src/main/java/im/conversations/android/database/model/Credential.java b/app/src/main/java/im/conversations/android/database/model/Credential.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/model/Credential.java
rename to app/src/main/java/im/conversations/android/database/model/Credential.java
diff --git a/src/main/java/im/conversations/android/database/model/MessageContent.java b/app/src/main/java/im/conversations/android/database/model/MessageContent.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/model/MessageContent.java
rename to app/src/main/java/im/conversations/android/database/model/MessageContent.java
diff --git a/src/main/java/im/conversations/android/database/model/MessageEmbedded.java b/app/src/main/java/im/conversations/android/database/model/MessageEmbedded.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/model/MessageEmbedded.java
rename to app/src/main/java/im/conversations/android/database/model/MessageEmbedded.java
diff --git a/src/main/java/im/conversations/android/database/model/MessageIdentifier.java b/app/src/main/java/im/conversations/android/database/model/MessageIdentifier.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/model/MessageIdentifier.java
rename to app/src/main/java/im/conversations/android/database/model/MessageIdentifier.java
diff --git a/src/main/java/im/conversations/android/database/model/MessageReaction.java b/app/src/main/java/im/conversations/android/database/model/MessageReaction.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/model/MessageReaction.java
rename to app/src/main/java/im/conversations/android/database/model/MessageReaction.java
diff --git a/src/main/java/im/conversations/android/database/model/MessageState.java b/app/src/main/java/im/conversations/android/database/model/MessageState.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/model/MessageState.java
rename to app/src/main/java/im/conversations/android/database/model/MessageState.java
diff --git a/src/main/java/im/conversations/android/database/model/MessageWithContentReactions.java b/app/src/main/java/im/conversations/android/database/model/MessageWithContentReactions.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/model/MessageWithContentReactions.java
rename to app/src/main/java/im/conversations/android/database/model/MessageWithContentReactions.java
diff --git a/src/main/java/im/conversations/android/database/model/Modification.java b/app/src/main/java/im/conversations/android/database/model/Modification.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/model/Modification.java
rename to app/src/main/java/im/conversations/android/database/model/Modification.java
diff --git a/src/main/java/im/conversations/android/database/model/PartType.java b/app/src/main/java/im/conversations/android/database/model/PartType.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/model/PartType.java
rename to app/src/main/java/im/conversations/android/database/model/PartType.java
diff --git a/src/main/java/im/conversations/android/database/model/PresenceShow.java b/app/src/main/java/im/conversations/android/database/model/PresenceShow.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/model/PresenceShow.java
rename to app/src/main/java/im/conversations/android/database/model/PresenceShow.java
diff --git a/src/main/java/im/conversations/android/database/model/PresenceType.java b/app/src/main/java/im/conversations/android/database/model/PresenceType.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/model/PresenceType.java
rename to app/src/main/java/im/conversations/android/database/model/PresenceType.java
diff --git a/src/main/java/im/conversations/android/database/model/Proxy.java b/app/src/main/java/im/conversations/android/database/model/Proxy.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/model/Proxy.java
rename to app/src/main/java/im/conversations/android/database/model/Proxy.java
diff --git a/src/main/java/im/conversations/android/database/model/StateType.java b/app/src/main/java/im/conversations/android/database/model/StateType.java
similarity index 100%
rename from src/main/java/im/conversations/android/database/model/StateType.java
rename to app/src/main/java/im/conversations/android/database/model/StateType.java
diff --git a/app/src/main/java/im/conversations/android/dns/AndroidUsingExecLowPriority.java b/app/src/main/java/im/conversations/android/dns/AndroidUsingExecLowPriority.java
new file mode 100644
index 000000000..09d13813d
--- /dev/null
+++ b/app/src/main/java/im/conversations/android/dns/AndroidUsingExecLowPriority.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2015-2016 the original author or authors
+ *
+ * This software is licensed under the Apache License, Version 2.0,
+ * the GNU Lesser General Public License version 2 or later ("LGPL")
+ * and the WTFPL.
+ * You may choose either license to govern your use of this software only
+ * upon the condition that you accept all of the terms of either
+ * the Apache License 2.0, the LGPL 2.1+ or the WTFPL.
+ */
+package im.conversations.android.dns;
+
+import de.measite.minidns.dnsserverlookup.AbstractDNSServerLookupMechanism;
+import de.measite.minidns.dnsserverlookup.AndroidUsingReflection;
+import de.measite.minidns.dnsserverlookup.DNSServerLookupMechanism;
+import de.measite.minidns.util.PlatformDetection;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.LineNumberReader;
+import java.net.InetAddress;
+import java.util.HashSet;
+import java.util.logging.Level;
+
+/** Try to retrieve the list of DNS server by executing getprop. */
+public class AndroidUsingExecLowPriority extends AbstractDNSServerLookupMechanism {
+
+ public static final DNSServerLookupMechanism INSTANCE = new AndroidUsingExecLowPriority();
+ public static final int PRIORITY = AndroidUsingReflection.PRIORITY + 1;
+
+ private AndroidUsingExecLowPriority() {
+ super(AndroidUsingExecLowPriority.class.getSimpleName(), PRIORITY);
+ }
+
+ @Override
+ public String[] getDnsServerAddresses() {
+ try {
+ Process process = Runtime.getRuntime().exec("getprop");
+ InputStream inputStream = process.getInputStream();
+ LineNumberReader lnr = new LineNumberReader(new InputStreamReader(inputStream));
+ String line;
+ HashSet server = new HashSet<>(6);
+ while ((line = lnr.readLine()) != null) {
+ int split = line.indexOf("]: [");
+ if (split == -1) {
+ continue;
+ }
+ String property = line.substring(1, split);
+ String value = line.substring(split + 4, line.length() - 1);
+
+ if (value.isEmpty()) {
+ continue;
+ }
+
+ if (property.endsWith(".dns")
+ || property.endsWith(".dns1")
+ || property.endsWith(".dns2")
+ || property.endsWith(".dns3")
+ || property.endsWith(".dns4")) {
+
+ // normalize the address
+
+ InetAddress ip = InetAddress.getByName(value);
+
+ if (ip == null) continue;
+
+ value = ip.getHostAddress();
+
+ if (value == null) continue;
+ if (value.length() == 0) continue;
+
+ server.add(value);
+ }
+ }
+ if (server.size() > 0) {
+ return server.toArray(new String[server.size()]);
+ }
+ } catch (IOException e) {
+ LOGGER.log(Level.WARNING, "Exception in findDNSByExec", e);
+ }
+ return null;
+ }
+
+ @Override
+ public boolean isAvailable() {
+ return PlatformDetection.isAndroid();
+ }
+}
diff --git a/src/main/java/eu/siacs/conversations/utils/AndroidUsingLinkProperties.java b/app/src/main/java/im/conversations/android/dns/AndroidUsingLinkProperties.java
similarity index 67%
rename from src/main/java/eu/siacs/conversations/utils/AndroidUsingLinkProperties.java
rename to app/src/main/java/im/conversations/android/dns/AndroidUsingLinkProperties.java
index 658e7abcd..e164f1b27 100644
--- a/src/main/java/eu/siacs/conversations/utils/AndroidUsingLinkProperties.java
+++ b/app/src/main/java/im/conversations/android/dns/AndroidUsingLinkProperties.java
@@ -1,22 +1,18 @@
-package eu.siacs.conversations.utils;
+package im.conversations.android.dns;
-import android.annotation.TargetApi;
import android.content.Context;
import android.net.ConnectivityManager;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkInfo;
import android.net.RouteInfo;
-import android.os.Build;
-
+import de.measite.minidns.dnsserverlookup.AbstractDNSServerLookupMechanism;
+import de.measite.minidns.dnsserverlookup.AndroidUsingExec;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.List;
-import de.measite.minidns.dnsserverlookup.AbstractDNSServerLookupMechanism;
-import de.measite.minidns.dnsserverlookup.AndroidUsingExec;
-
public class AndroidUsingLinkProperties extends AbstractDNSServerLookupMechanism {
private final Context context;
@@ -28,47 +24,47 @@ public class AndroidUsingLinkProperties extends AbstractDNSServerLookupMechanism
@Override
public boolean isAvailable() {
- return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP;
+ return true;
}
@Override
- @TargetApi(21)
public String[] getDnsServerAddresses() {
- final ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
- final Network[] networks = connectivityManager == null ? null : connectivityManager.getAllNetworks();
+ final ConnectivityManager connectivityManager =
+ (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
+ final Network[] networks =
+ connectivityManager == null ? null : connectivityManager.getAllNetworks();
if (networks == null) {
return new String[0];
}
- final Network activeNetwork = getActiveNetwork(connectivityManager);
+ final Network activeNetwork = connectivityManager.getActiveNetwork();
final List servers = new ArrayList<>();
int vpnOffset = 0;
- for(Network network : networks) {
+ for (Network network : networks) {
LinkProperties linkProperties = connectivityManager.getLinkProperties(network);
if (linkProperties == null) {
continue;
}
final NetworkInfo networkInfo = connectivityManager.getNetworkInfo(network);
final boolean isActiveNetwork = network.equals(activeNetwork);
- final boolean isVpn = networkInfo != null && networkInfo.getType() == ConnectivityManager.TYPE_VPN;
+ final boolean isVpn =
+ networkInfo != null && networkInfo.getType() == ConnectivityManager.TYPE_VPN;
if (isActiveNetwork && isVpn) {
final List tmp = getIPv4First(linkProperties.getDnsServers());
servers.addAll(0, tmp);
vpnOffset += tmp.size();
- } else if (hasDefaultRoute(linkProperties) || isActiveNetwork || activeNetwork == null || isVpn) {
+ } else if (hasDefaultRoute(linkProperties)
+ || isActiveNetwork
+ || activeNetwork == null
+ || isVpn) {
servers.addAll(vpnOffset, getIPv4First(linkProperties.getDnsServers()));
}
}
return servers.toArray(new String[0]);
}
- @TargetApi(23)
- private static Network getActiveNetwork(ConnectivityManager cm) {
- return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? cm.getActiveNetwork() : null;
- }
-
private static List getIPv4First(List in) {
List out = new ArrayList<>();
- for(InetAddress address : in) {
+ for (InetAddress address : in) {
if (address instanceof Inet4Address) {
out.add(0, address.getHostAddress());
} else {
@@ -78,9 +74,8 @@ public class AndroidUsingLinkProperties extends AbstractDNSServerLookupMechanism
return out;
}
- @TargetApi(Build.VERSION_CODES.LOLLIPOP)
private static boolean hasDefaultRoute(LinkProperties linkProperties) {
- for(RouteInfo route: linkProperties.getRoutes()) {
+ for (RouteInfo route : linkProperties.getRoutes()) {
if (route.isDefaultRoute()) {
return true;
}
diff --git a/app/src/main/java/im/conversations/android/dns/IP.java b/app/src/main/java/im/conversations/android/dns/IP.java
new file mode 100644
index 000000000..b87e83561
--- /dev/null
+++ b/app/src/main/java/im/conversations/android/dns/IP.java
@@ -0,0 +1,39 @@
+package im.conversations.android.dns;
+
+import java.util.regex.Pattern;
+
+public class IP {
+
+ private static final Pattern PATTERN_IPV4 =
+ Pattern.compile(
+ "\\A(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z");
+ private static final Pattern PATTERN_IPV6_HEX4DECCOMPRESSED =
+ Pattern.compile(
+ "\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)"
+ + " ::((?:[0-9A-Fa-f]{1,4}:)*)(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z");
+ private static final Pattern PATTERN_IPV6_6HEX4DEC =
+ Pattern.compile(
+ "\\A((?:[0-9A-Fa-f]{1,4}:){6,6})(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)(\\.(25[0-5]|2[0-4]\\d|[0-1]?\\d?\\d)){3}\\z");
+ private static final Pattern PATTERN_IPV6_HEXCOMPRESSED =
+ Pattern.compile(
+ "\\A((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)\\z");
+ private static final Pattern PATTERN_IPV6 =
+ Pattern.compile("\\A(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}\\z");
+
+ public static boolean matches(String server) {
+ return server != null
+ && (PATTERN_IPV4.matcher(server).matches()
+ || PATTERN_IPV6.matcher(server).matches()
+ || PATTERN_IPV6_6HEX4DEC.matcher(server).matches()
+ || PATTERN_IPV6_HEX4DECCOMPRESSED.matcher(server).matches()
+ || PATTERN_IPV6_HEXCOMPRESSED.matcher(server).matches());
+ }
+
+ public static String wrapIPv6(final String host) {
+ if (matches(host)) {
+ return String.format("[%s]", host);
+ } else {
+ return host;
+ }
+ }
+}
diff --git a/src/main/java/eu/siacs/conversations/utils/Resolver.java b/app/src/main/java/im/conversations/android/dns/Resolver.java
similarity index 67%
rename from src/main/java/eu/siacs/conversations/utils/Resolver.java
rename to app/src/main/java/im/conversations/android/dns/Resolver.java
index 463d6eb73..9a085d93a 100644
--- a/src/main/java/eu/siacs/conversations/utils/Resolver.java
+++ b/app/src/main/java/im/conversations/android/dns/Resolver.java
@@ -1,20 +1,10 @@
-package eu.siacs.conversations.utils;
+package im.conversations.android.dns;
+import android.app.Application;
import android.content.ContentValues;
+import android.content.Context;
import android.database.Cursor;
-import android.util.Log;
-
import androidx.annotation.NonNull;
-
-import java.io.IOException;
-import java.lang.reflect.Field;
-import java.net.Inet4Address;
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
import de.measite.minidns.AbstractDNSClient;
import de.measite.minidns.DNSCache;
import de.measite.minidns.DNSClient;
@@ -34,26 +24,34 @@ import de.measite.minidns.record.CNAME;
import de.measite.minidns.record.Data;
import de.measite.minidns.record.InternetAddressRR;
import de.measite.minidns.record.SRV;
-import eu.siacs.conversations.Config;
-import eu.siacs.conversations.R;
-import eu.siacs.conversations.services.XmppConnectionService;
-import eu.siacs.conversations.xmpp.Jid;
+import java.io.IOException;
+import java.lang.reflect.Field;
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import org.jxmpp.jid.DomainJid;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
public class Resolver {
+ private static final Logger LOGGER = LoggerFactory.getLogger(Resolver.class);
+
public static final int DEFAULT_PORT_XMPP = 5222;
private static final String DIRECT_TLS_SERVICE = "_xmpps-client";
private static final String STARTTLS_SERVICE = "_xmpp-client";
- private static XmppConnectionService SERVICE = null;
+ private static Context SERVICE;
-
- public static void init(XmppConnectionService service) {
- Resolver.SERVICE = service;
+ public static void init(final Application application) {
+ SERVICE = application.getApplicationContext();
DNSClient.removeDNSServerLookupMechanism(AndroidUsingExec.INSTANCE);
DNSClient.addDnsServerLookupMechanism(AndroidUsingExecLowPriority.INSTANCE);
- DNSClient.addDnsServerLookupMechanism(new AndroidUsingLinkProperties(service));
+ DNSClient.addDnsServerLookupMechanism(new AndroidUsingLinkProperties(application));
final AbstractDNSClient client = ResolverApi.INSTANCE.getClient();
if (client instanceof ReliableDNSClient) {
disableHardcodedDnsServers((ReliableDNSClient) client);
@@ -68,11 +66,12 @@ public class Resolver {
if (dnsClient != null) {
dnsClient.getDataSource().setTimeout(3000);
}
- final Field useHardcodedDnsServers = DNSClient.class.getDeclaredField("useHardcodedDnsServers");
+ final Field useHardcodedDnsServers =
+ DNSClient.class.getDeclaredField("useHardcodedDnsServers");
useHardcodedDnsServers.setAccessible(true);
useHardcodedDnsServers.setBoolean(dnsClient, false);
- } catch (NoSuchFieldException | IllegalAccessException e) {
- Log.e(Config.LOGTAG, "Unable to disable hardcoded DNS servers", e);
+ } catch (final NoSuchFieldException | IllegalAccessException e) {
+ LOGGER.error("Unable to disable hardcoded DNS servers", e);
}
}
@@ -85,7 +84,7 @@ public class Resolver {
return Collections.singletonList(result);
}
- public static void checkDomain(final Jid jid) {
+ public static void checkDomain(final DomainJid jid) {
DNSName.from(jid.getDomain());
}
@@ -102,50 +101,57 @@ public class Resolver {
final AbstractDNSClient client = ResolverApi.INSTANCE.getClient();
final DNSCache dnsCache = client.getCache();
if (dnsCache instanceof LRUCache) {
- Log.d(Config.LOGTAG,"clearing DNS cache");
+ LOGGER.debug("clearing DNS cache");
((LRUCache) dnsCache).clear();
}
}
-
public static boolean useDirectTls(final int port) {
return port == 443 || port == 5223;
}
public static List resolve(String domain) {
- final List ipResults = fromIpAddress(domain);
+ final List ipResults = fromIpAddress(domain);
if (ipResults.size() > 0) {
return ipResults;
}
final List results = new ArrayList<>();
final List fallbackResults = new ArrayList<>();
final Thread[] threads = new Thread[3];
- threads[0] = new Thread(() -> {
- try {
- final List list = resolveSrv(domain, true);
- synchronized (results) {
- results.addAll(list);
- }
- } catch (Throwable throwable) {
- Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": error resolving SRV record (direct TLS)", throwable);
- }
- });
- threads[1] = new Thread(() -> {
- try {
- final List list = resolveSrv(domain, false);
- synchronized (results) {
- results.addAll(list);
- }
- } catch (Throwable throwable) {
- Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": error resolving SRV record (STARTTLS)", throwable);
- }
- });
- threads[2] = new Thread(() -> {
- List list = resolveNoSrvRecords(DNSName.from(domain), true);
- synchronized (fallbackResults) {
- fallbackResults.addAll(list);
- }
- });
+ threads[0] =
+ new Thread(
+ () -> {
+ try {
+ final List list = resolveSrv(domain, true);
+ synchronized (results) {
+ results.addAll(list);
+ }
+ } catch (final Throwable throwable) {
+ LOGGER.debug("error resolving SRV record (direct TLS)", throwable);
+ }
+ });
+ threads[1] =
+ new Thread(
+ () -> {
+ try {
+ final List list = resolveSrv(domain, false);
+ synchronized (results) {
+ results.addAll(list);
+ }
+
+ } catch (Throwable throwable) {
+ LOGGER.debug(
+ "error resolving SRV record (direct STARTTLS)", throwable);
+ }
+ });
+ threads[2] =
+ new Thread(
+ () -> {
+ List list = resolveNoSrvRecords(DNSName.from(domain), true);
+ synchronized (fallbackResults) {
+ fallbackResults.addAll(list);
+ }
+ });
for (final Thread thread : threads) {
thread.start();
}
@@ -156,14 +162,14 @@ public class Resolver {
threads[2].interrupt();
synchronized (results) {
Collections.sort(results);
- Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": " + results.toString());
+ LOGGER.info("{}", results);
return new ArrayList<>(results);
}
} else {
threads[2].join();
synchronized (fallbackResults) {
Collections.sort(fallbackResults);
- Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": " + fallbackResults.toString());
+ LOGGER.info("fallback {}", fallbackResults);
return new ArrayList<>(fallbackResults);
}
}
@@ -189,8 +195,11 @@ public class Resolver {
}
}
- private static List resolveSrv(String domain, final boolean directTls) throws IOException {
- DNSName dnsName = DNSName.from((directTls ? DIRECT_TLS_SERVICE : STARTTLS_SERVICE) + "._tcp." + domain);
+ private static List resolveSrv(String domain, final boolean directTls)
+ throws IOException {
+ DNSName dnsName =
+ DNSName.from(
+ (directTls ? DIRECT_TLS_SERVICE : STARTTLS_SERVICE) + "._tcp." + domain);
ResolverResult result = resolveWithFallback(dnsName, SRV.class);
final List results = new ArrayList<>();
final List threads = new ArrayList<>();
@@ -198,24 +207,37 @@ public class Resolver {
if (record.name.length() == 0 && record.priority == 0) {
continue;
}
- threads.add(new Thread(() -> {
- final List ipv4s = resolveIp(record, A.class, result.isAuthenticData(), directTls);
- if (ipv4s.size() == 0) {
- Result resolverResult = Result.fromRecord(record, directTls);
- resolverResult.authenticated = result.isAuthenticData();
- ipv4s.add(resolverResult);
- }
- synchronized (results) {
- results.addAll(ipv4s);
- }
-
- }));
- threads.add(new Thread(() -> {
- final List ipv6s = resolveIp(record, AAAA.class, result.isAuthenticData(), directTls);
- synchronized (results) {
- results.addAll(ipv6s);
- }
- }));
+ threads.add(
+ new Thread(
+ () -> {
+ final List ipv4s =
+ resolveIp(
+ record,
+ A.class,
+ result.isAuthenticData(),
+ directTls);
+ if (ipv4s.size() == 0) {
+ Result resolverResult = Result.fromRecord(record, directTls);
+ resolverResult.authenticated = result.isAuthenticData();
+ ipv4s.add(resolverResult);
+ }
+ synchronized (results) {
+ results.addAll(ipv4s);
+ }
+ }));
+ threads.add(
+ new Thread(
+ () -> {
+ final List ipv6s =
+ resolveIp(
+ record,
+ AAAA.class,
+ result.isAuthenticData(),
+ directTls);
+ synchronized (results) {
+ results.addAll(ipv6s);
+ }
+ }));
}
for (Thread thread : threads) {
thread.start();
@@ -230,18 +252,22 @@ public class Resolver {
return results;
}
- private static List resolveIp(SRV srv, Class type, boolean authenticated, boolean directTls) {
+ private static List resolveIp(
+ SRV srv, Class type, boolean authenticated, boolean directTls) {
List list = new ArrayList<>();
try {
ResolverResult results = resolveWithFallback(srv.name, type, authenticated);
for (D record : results.getAnswersOrEmptySet()) {
Result resolverResult = Result.fromRecord(srv, directTls);
- resolverResult.authenticated = results.isAuthenticData() && authenticated; //TODO technically it doesn’t matter if the IP was authenticated
+ resolverResult.authenticated =
+ results.isAuthenticData()
+ && authenticated; // TODO technically it doesn’t matter if the IP
+ // was authenticated
resolverResult.ip = record.getInetAddress();
list.add(resolverResult);
}
- } catch (Throwable t) {
- Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": error resolving " + type.getSimpleName() + " " + t.getMessage());
+ } catch (final Throwable t) {
+ LOGGER.info("error resolving {}", type.getSimpleName(), t);
}
return list;
}
@@ -252,26 +278,30 @@ public class Resolver {
for (A a : resolveWithFallback(dnsName, A.class, false).getAnswersOrEmptySet()) {
results.add(Result.createDefault(dnsName, a.getInetAddress()));
}
- for (AAAA aaaa : resolveWithFallback(dnsName, AAAA.class, false).getAnswersOrEmptySet()) {
+ for (AAAA aaaa :
+ resolveWithFallback(dnsName, AAAA.class, false).getAnswersOrEmptySet()) {
results.add(Result.createDefault(dnsName, aaaa.getInetAddress()));
}
if (results.size() == 0 && withCnames) {
- for (CNAME cname : resolveWithFallback(dnsName, CNAME.class, false).getAnswersOrEmptySet()) {
+ for (CNAME cname :
+ resolveWithFallback(dnsName, CNAME.class, false).getAnswersOrEmptySet()) {
results.addAll(resolveNoSrvRecords(cname.name, false));
}
}
} catch (Throwable throwable) {
- Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + "error resolving fallback records", throwable);
+ LOGGER.info("Error resolving fallback records", throwable);
}
results.add(Result.createDefault(dnsName));
return results;
}
- private static ResolverResult resolveWithFallback(DNSName dnsName, Class type) throws IOException {
+ private static ResolverResult resolveWithFallback(
+ DNSName dnsName, Class type) throws IOException {
return resolveWithFallback(dnsName, type, validateHostname());
}
- private static ResolverResult resolveWithFallback(DNSName dnsName, Class type, boolean validateHostname) throws IOException {
+ private static ResolverResult resolveWithFallback(
+ DNSName dnsName, Class type, boolean validateHostname) throws IOException {
final Question question = new Question(dnsName, Record.TYPE.getType(type));
if (!validateHostname) {
return ResolverApi.INSTANCE.resolve(question);
@@ -279,17 +309,22 @@ public class Resolver {
try {
return DnssecResolverApi.INSTANCE.resolveDnssecReliable(question);
} catch (DNSSECResultNotAuthenticException e) {
- Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": error resolving " + type.getSimpleName() + " with DNSSEC. Trying DNS instead.", e);
+ LOGGER.info(
+ "Error resolving {} with DNSSEC. Trying DNS instead", type.getSimpleName(), e);
} catch (IOException e) {
throw e;
} catch (Throwable throwable) {
- Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": error resolving " + type.getSimpleName() + " with DNSSEC. Trying DNS instead.", throwable);
+ LOGGER.info(
+ "Error resolving {} with DNSSEC. Trying DNS instead",
+ type.getSimpleName(),
+ throwable);
}
return ResolverApi.INSTANCE.resolve(question);
}
private static boolean validateHostname() {
- return SERVICE != null && SERVICE.getBooleanPreference("validate_hostname", R.bool.validate_hostname);
+ // TODO bring back in one form or another
+ return false;
}
public static class Result implements Comparable {
@@ -392,14 +427,22 @@ public class Resolver {
@Override
public String toString() {
- return "Result{" +
- "ip='" + (ip == null ? null : ip.getHostAddress()) + '\'' +
- ", hostame='" + (hostname == null ? null : hostname.toString()) + '\'' +
- ", port=" + port +
- ", directTls=" + directTls +
- ", authenticated=" + authenticated +
- ", priority=" + priority +
- '}';
+ return "Result{"
+ + "ip='"
+ + (ip == null ? null : ip.getHostAddress())
+ + '\''
+ + ", hostame='"
+ + (hostname == null ? null : hostname.toString())
+ + '\''
+ + ", port="
+ + port
+ + ", directTls="
+ + directTls
+ + ", authenticated="
+ + authenticated
+ + ", priority="
+ + priority
+ + '}';
}
@Override
@@ -436,5 +479,4 @@ public class Resolver {
return contentValues;
}
}
-
}
diff --git a/src/main/java/im/conversations/android/repository/AbstractRepository.java b/app/src/main/java/im/conversations/android/repository/AbstractRepository.java
similarity index 100%
rename from src/main/java/im/conversations/android/repository/AbstractRepository.java
rename to app/src/main/java/im/conversations/android/repository/AbstractRepository.java
diff --git a/src/main/java/im/conversations/android/repository/AccountRepository.java b/app/src/main/java/im/conversations/android/repository/AccountRepository.java
similarity index 100%
rename from src/main/java/im/conversations/android/repository/AccountRepository.java
rename to app/src/main/java/im/conversations/android/repository/AccountRepository.java
diff --git a/src/main/java/eu/siacs/conversations/utils/SocksSocketFactory.java b/app/src/main/java/im/conversations/android/socks/SocksSocketFactory.java
similarity index 77%
rename from src/main/java/eu/siacs/conversations/utils/SocksSocketFactory.java
rename to app/src/main/java/im/conversations/android/socks/SocksSocketFactory.java
index 2b9d42d7a..aa01901ad 100644
--- a/src/main/java/eu/siacs/conversations/utils/SocksSocketFactory.java
+++ b/app/src/main/java/im/conversations/android/socks/SocksSocketFactory.java
@@ -1,7 +1,7 @@
-package eu.siacs.conversations.utils;
+package im.conversations.android.socks;
import com.google.common.io.ByteStreams;
-
+import im.conversations.android.xmpp.ConnectionPool;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -10,17 +10,16 @@ import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
-import eu.siacs.conversations.Config;
-
public class SocksSocketFactory {
- private static final byte[] LOCALHOST = new byte[]{127, 0, 0, 1};
+ private static final byte[] LOCALHOST = new byte[] {127, 0, 0, 1};
- public static void createSocksConnection(final Socket socket, final String destination, final int port) throws IOException {
- //TODO use different Socks Addr Type if destination is IP or IPv6
+ public static void createSocksConnection(
+ final Socket socket, final String destination, final int port) throws IOException {
+ // TODO use different Socks Addr Type if destination is IP or IPv6
final InputStream proxyIs = socket.getInputStream();
final OutputStream proxyOs = socket.getOutputStream();
- proxyOs.write(new byte[]{0x05, 0x01, 0x00});
+ proxyOs.write(new byte[] {0x05, 0x01, 0x00});
proxyOs.flush();
final byte[] handshake = new byte[2];
ByteStreams.readFully(proxyIs, handshake);
@@ -29,7 +28,7 @@ public class SocksSocketFactory {
}
final byte[] dest = destination.getBytes();
final ByteBuffer request = ByteBuffer.allocate(7 + dest.length);
- request.put(new byte[]{0x05, 0x01, 0x00, 0x03});
+ request.put(new byte[] {0x05, 0x01, 0x00, 0x03});
request.put((byte) dest.length);
request.put(dest);
request.putShort((short) port);
@@ -48,7 +47,10 @@ public class SocksSocketFactory {
if (bndAddrType == 0x03) {
final String receivedDestination = new String(bndDestination);
if (!receivedDestination.equalsIgnoreCase(destination)) {
- throw new IOException(String.format("Destination mismatch. Received %s Expected %s", receivedDestination, destination));
+ throw new IOException(
+ String.format(
+ "Destination mismatch. Received %s Expected %s",
+ receivedDestination, destination));
}
}
ByteStreams.readFully(proxyIs, bndPort);
@@ -63,7 +65,8 @@ public class SocksSocketFactory {
}
}
- private static byte[] readDestination(final byte type, final InputStream inputStream) throws IOException {
+ private static byte[] readDestination(final byte type, final InputStream inputStream)
+ throws IOException {
final byte[] bndDestination;
if (type == 0x01) {
bndDestination = new byte[4];
@@ -88,10 +91,11 @@ public class SocksSocketFactory {
return false;
}
- private static Socket createSocket(InetSocketAddress address, String destination, int port) throws IOException {
+ private static Socket createSocket(InetSocketAddress address, String destination, int port)
+ throws IOException {
Socket socket = new Socket();
try {
- socket.connect(address, Config.CONNECT_TIMEOUT * 1000);
+ socket.connect(address, ConnectionPool.CONNECT_TIMEOUT * 1000);
} catch (IOException e) {
throw new SocksProxyNotFoundException();
}
@@ -100,7 +104,10 @@ public class SocksSocketFactory {
}
public static Socket createSocketOverTor(String destination, int port) throws IOException {
- return createSocket(new InetSocketAddress(InetAddress.getByAddress(LOCALHOST), 9050), destination, port);
+ return createSocket(
+ new InetSocketAddress(InetAddress.getByAddress(LOCALHOST), 9050),
+ destination,
+ port);
}
private static class SocksConnectionException extends IOException {
@@ -109,9 +116,7 @@ public class SocksSocketFactory {
}
}
- public static class SocksProxyNotFoundException extends IOException {
-
- }
+ public static class SocksProxyNotFoundException extends IOException {}
public static class HostNotFoundException extends SocksConnectionException {
HostNotFoundException(String message) {
diff --git a/src/main/java/im/conversations/android/tls/SSLSockets.java b/app/src/main/java/im/conversations/android/tls/SSLSockets.java
similarity index 100%
rename from src/main/java/im/conversations/android/tls/SSLSockets.java
rename to app/src/main/java/im/conversations/android/tls/SSLSockets.java
diff --git a/app/src/main/java/im/conversations/android/tls/TrustManagers.java b/app/src/main/java/im/conversations/android/tls/TrustManagers.java
new file mode 100644
index 000000000..a7a7aa8f2
--- /dev/null
+++ b/app/src/main/java/im/conversations/android/tls/TrustManagers.java
@@ -0,0 +1,29 @@
+package im.conversations.android.tls;
+
+import java.security.KeyStore;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509TrustManager;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class TrustManagers {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(TrustManagers.class);
+
+ public static X509TrustManager getTrustManager() {
+ try {
+ final TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
+ tmf.init((KeyStore) null);
+ for (final TrustManager t : tmf.getTrustManagers()) {
+ if (t instanceof X509TrustManager) {
+ return (X509TrustManager) t;
+ }
+ }
+ return null;
+ } catch (final Exception e) {
+ LOGGER.info("Could not get default Trust Manager");
+ return null;
+ }
+ }
+}
diff --git a/src/main/java/im/conversations/android/tls/XmppDomainVerifier.java b/app/src/main/java/im/conversations/android/tls/XmppDomainVerifier.java
similarity index 100%
rename from src/main/java/im/conversations/android/tls/XmppDomainVerifier.java
rename to app/src/main/java/im/conversations/android/tls/XmppDomainVerifier.java
diff --git a/src/main/java/im/conversations/android/transformer/Transformation.java b/app/src/main/java/im/conversations/android/transformer/Transformation.java
similarity index 100%
rename from src/main/java/im/conversations/android/transformer/Transformation.java
rename to app/src/main/java/im/conversations/android/transformer/Transformation.java
diff --git a/src/main/java/im/conversations/android/transformer/TransformationFactory.java b/app/src/main/java/im/conversations/android/transformer/TransformationFactory.java
similarity index 100%
rename from src/main/java/im/conversations/android/transformer/TransformationFactory.java
rename to app/src/main/java/im/conversations/android/transformer/TransformationFactory.java
diff --git a/src/main/java/im/conversations/android/transformer/Transformer.java b/app/src/main/java/im/conversations/android/transformer/Transformer.java
similarity index 100%
rename from src/main/java/im/conversations/android/transformer/Transformer.java
rename to app/src/main/java/im/conversations/android/transformer/Transformer.java
diff --git a/src/main/java/im/conversations/android/ui/Activities.java b/app/src/main/java/im/conversations/android/ui/Activities.java
similarity index 100%
rename from src/main/java/im/conversations/android/ui/Activities.java
rename to app/src/main/java/im/conversations/android/ui/Activities.java
diff --git a/src/main/java/im/conversations/android/ui/BindingAdapters.java b/app/src/main/java/im/conversations/android/ui/BindingAdapters.java
similarity index 100%
rename from src/main/java/im/conversations/android/ui/BindingAdapters.java
rename to app/src/main/java/im/conversations/android/ui/BindingAdapters.java
diff --git a/src/main/java/im/conversations/android/ui/Event.java b/app/src/main/java/im/conversations/android/ui/Event.java
similarity index 100%
rename from src/main/java/im/conversations/android/ui/Event.java
rename to app/src/main/java/im/conversations/android/ui/Event.java
diff --git a/src/main/java/im/conversations/android/ui/NavControllers.java b/app/src/main/java/im/conversations/android/ui/NavControllers.java
similarity index 100%
rename from src/main/java/im/conversations/android/ui/NavControllers.java
rename to app/src/main/java/im/conversations/android/ui/NavControllers.java
diff --git a/src/main/java/im/conversations/android/ui/activity/SetupActivity.java b/app/src/main/java/im/conversations/android/ui/activity/SetupActivity.java
similarity index 90%
rename from src/main/java/im/conversations/android/ui/activity/SetupActivity.java
rename to app/src/main/java/im/conversations/android/ui/activity/SetupActivity.java
index ca814d840..6ecc943bc 100644
--- a/src/main/java/im/conversations/android/ui/activity/SetupActivity.java
+++ b/app/src/main/java/im/conversations/android/ui/activity/SetupActivity.java
@@ -2,12 +2,13 @@ package im.conversations.android.ui.activity;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
+import androidx.appcompat.app.AppCompatDelegate;
import androidx.databinding.DataBindingUtil;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavController;
-import eu.siacs.conversations.R;
-import eu.siacs.conversations.SetupNavigationDirections;
-import eu.siacs.conversations.databinding.ActivitySetupBinding;
+import im.conversations.android.R;
+import im.conversations.android.SetupNavigationDirections;
+import im.conversations.android.databinding.ActivitySetupBinding;
import im.conversations.android.ui.Activities;
import im.conversations.android.ui.Event;
import im.conversations.android.ui.NavControllers;
diff --git a/src/main/java/im/conversations/android/ui/fragment/setup/AbstractSetupFragment.java b/app/src/main/java/im/conversations/android/ui/fragment/setup/AbstractSetupFragment.java
similarity index 100%
rename from src/main/java/im/conversations/android/ui/fragment/setup/AbstractSetupFragment.java
rename to app/src/main/java/im/conversations/android/ui/fragment/setup/AbstractSetupFragment.java
diff --git a/src/main/java/im/conversations/android/ui/fragment/setup/HostnameFragment.java b/app/src/main/java/im/conversations/android/ui/fragment/setup/HostnameFragment.java
similarity index 100%
rename from src/main/java/im/conversations/android/ui/fragment/setup/HostnameFragment.java
rename to app/src/main/java/im/conversations/android/ui/fragment/setup/HostnameFragment.java
diff --git a/src/main/java/im/conversations/android/ui/fragment/setup/PasswordFragment.java b/app/src/main/java/im/conversations/android/ui/fragment/setup/PasswordFragment.java
similarity index 88%
rename from src/main/java/im/conversations/android/ui/fragment/setup/PasswordFragment.java
rename to app/src/main/java/im/conversations/android/ui/fragment/setup/PasswordFragment.java
index 389aaca59..dda4f20a5 100644
--- a/src/main/java/im/conversations/android/ui/fragment/setup/PasswordFragment.java
+++ b/app/src/main/java/im/conversations/android/ui/fragment/setup/PasswordFragment.java
@@ -6,8 +6,8 @@ import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.databinding.DataBindingUtil;
-import eu.siacs.conversations.R;
-import eu.siacs.conversations.databinding.FragmentPasswordBinding;
+import im.conversations.android.R;
+import im.conversations.android.databinding.FragmentPasswordBinding;
public class PasswordFragment extends AbstractSetupFragment {
diff --git a/src/main/java/im/conversations/android/ui/fragment/setup/SignInFragment.java b/app/src/main/java/im/conversations/android/ui/fragment/setup/SignInFragment.java
similarity index 88%
rename from src/main/java/im/conversations/android/ui/fragment/setup/SignInFragment.java
rename to app/src/main/java/im/conversations/android/ui/fragment/setup/SignInFragment.java
index dac120a11..3bb96f74c 100644
--- a/src/main/java/im/conversations/android/ui/fragment/setup/SignInFragment.java
+++ b/app/src/main/java/im/conversations/android/ui/fragment/setup/SignInFragment.java
@@ -6,8 +6,8 @@ import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.databinding.DataBindingUtil;
-import eu.siacs.conversations.R;
-import eu.siacs.conversations.databinding.FragmentSignInBinding;
+import im.conversations.android.R;
+import im.conversations.android.databinding.FragmentSignInBinding;
public class SignInFragment extends AbstractSetupFragment {
diff --git a/src/main/java/im/conversations/android/ui/model/SetupViewModel.java b/app/src/main/java/im/conversations/android/ui/model/SetupViewModel.java
similarity index 100%
rename from src/main/java/im/conversations/android/ui/model/SetupViewModel.java
rename to app/src/main/java/im/conversations/android/ui/model/SetupViewModel.java
diff --git a/src/main/java/eu/siacs/conversations/ui/util/PendingItem.java b/app/src/main/java/im/conversations/android/util/PendingItem.java
similarity index 98%
rename from src/main/java/eu/siacs/conversations/ui/util/PendingItem.java
rename to app/src/main/java/im/conversations/android/util/PendingItem.java
index a02281f77..0a5d94f13 100644
--- a/src/main/java/eu/siacs/conversations/ui/util/PendingItem.java
+++ b/app/src/main/java/im/conversations/android/util/PendingItem.java
@@ -27,7 +27,7 @@
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-package eu.siacs.conversations.ui.util;
+package im.conversations.android.util;
import java.util.function.Supplier;
diff --git a/src/main/java/im/conversations/android/xml/Element.java b/app/src/main/java/im/conversations/android/xml/Element.java
similarity index 100%
rename from src/main/java/im/conversations/android/xml/Element.java
rename to app/src/main/java/im/conversations/android/xml/Element.java
diff --git a/src/main/java/im/conversations/android/xml/Entities.java b/app/src/main/java/im/conversations/android/xml/Entities.java
similarity index 100%
rename from src/main/java/im/conversations/android/xml/Entities.java
rename to app/src/main/java/im/conversations/android/xml/Entities.java
diff --git a/src/main/java/im/conversations/android/xml/Namespace.java b/app/src/main/java/im/conversations/android/xml/Namespace.java
similarity index 100%
rename from src/main/java/im/conversations/android/xml/Namespace.java
rename to app/src/main/java/im/conversations/android/xml/Namespace.java
diff --git a/src/main/java/im/conversations/android/xml/Tag.java b/app/src/main/java/im/conversations/android/xml/Tag.java
similarity index 100%
rename from src/main/java/im/conversations/android/xml/Tag.java
rename to app/src/main/java/im/conversations/android/xml/Tag.java
diff --git a/src/main/java/im/conversations/android/xml/TagWriter.java b/app/src/main/java/im/conversations/android/xml/TagWriter.java
similarity index 100%
rename from src/main/java/im/conversations/android/xml/TagWriter.java
rename to app/src/main/java/im/conversations/android/xml/TagWriter.java
diff --git a/src/main/java/im/conversations/android/xml/XmlElementReader.java b/app/src/main/java/im/conversations/android/xml/XmlElementReader.java
similarity index 100%
rename from src/main/java/im/conversations/android/xml/XmlElementReader.java
rename to app/src/main/java/im/conversations/android/xml/XmlElementReader.java
diff --git a/src/main/java/im/conversations/android/xml/XmlReader.java b/app/src/main/java/im/conversations/android/xml/XmlReader.java
similarity index 100%
rename from src/main/java/im/conversations/android/xml/XmlReader.java
rename to app/src/main/java/im/conversations/android/xml/XmlReader.java
diff --git a/src/main/java/im/conversations/android/xmpp/Closables.java b/app/src/main/java/im/conversations/android/xmpp/Closables.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/Closables.java
rename to app/src/main/java/im/conversations/android/xmpp/Closables.java
diff --git a/src/main/java/im/conversations/android/xmpp/ConnectionException.java b/app/src/main/java/im/conversations/android/xmpp/ConnectionException.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/ConnectionException.java
rename to app/src/main/java/im/conversations/android/xmpp/ConnectionException.java
diff --git a/src/main/java/im/conversations/android/xmpp/ConnectionPool.java b/app/src/main/java/im/conversations/android/xmpp/ConnectionPool.java
similarity index 99%
rename from src/main/java/im/conversations/android/xmpp/ConnectionPool.java
rename to app/src/main/java/im/conversations/android/xmpp/ConnectionPool.java
index a57b5e6ab..41b1bbd32 100644
--- a/src/main/java/im/conversations/android/xmpp/ConnectionPool.java
+++ b/app/src/main/java/im/conversations/android/xmpp/ConnectionPool.java
@@ -31,6 +31,7 @@ public class ConnectionPool {
private static final Logger LOGGER = LoggerFactory.getLogger(ConnectionPool.class);
+ public static final int SOCKET_TIMEOUT = 15;
public static final int CONNECT_TIMEOUT = 90;
public static final int PING_MAX_INTERVAL = 300;
public static final int PING_MIN_INTERVAL = 30;
diff --git a/src/main/java/im/conversations/android/xmpp/ConnectionState.java b/app/src/main/java/im/conversations/android/xmpp/ConnectionState.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/ConnectionState.java
rename to app/src/main/java/im/conversations/android/xmpp/ConnectionState.java
diff --git a/src/main/java/im/conversations/android/xmpp/Entity.java b/app/src/main/java/im/conversations/android/xmpp/Entity.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/Entity.java
rename to app/src/main/java/im/conversations/android/xmpp/Entity.java
diff --git a/src/main/java/im/conversations/android/xmpp/EntityCapabilities.java b/app/src/main/java/im/conversations/android/xmpp/EntityCapabilities.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/EntityCapabilities.java
rename to app/src/main/java/im/conversations/android/xmpp/EntityCapabilities.java
diff --git a/src/main/java/im/conversations/android/xmpp/EntityCapabilities2.java b/app/src/main/java/im/conversations/android/xmpp/EntityCapabilities2.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/EntityCapabilities2.java
rename to app/src/main/java/im/conversations/android/xmpp/EntityCapabilities2.java
diff --git a/src/main/java/im/conversations/android/xmpp/ExtensionFactory.java b/app/src/main/java/im/conversations/android/xmpp/ExtensionFactory.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/ExtensionFactory.java
rename to app/src/main/java/im/conversations/android/xmpp/ExtensionFactory.java
diff --git a/src/main/java/im/conversations/android/xmpp/IqErrorException.java b/app/src/main/java/im/conversations/android/xmpp/IqErrorException.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/IqErrorException.java
rename to app/src/main/java/im/conversations/android/xmpp/IqErrorException.java
diff --git a/src/main/java/im/conversations/android/xmpp/Managers.java b/app/src/main/java/im/conversations/android/xmpp/Managers.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/Managers.java
rename to app/src/main/java/im/conversations/android/xmpp/Managers.java
diff --git a/src/main/java/im/conversations/android/xmpp/NodeConfiguration.java b/app/src/main/java/im/conversations/android/xmpp/NodeConfiguration.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/NodeConfiguration.java
rename to app/src/main/java/im/conversations/android/xmpp/NodeConfiguration.java
diff --git a/src/main/java/im/conversations/android/xmpp/PreconditionNotMetException.java b/app/src/main/java/im/conversations/android/xmpp/PreconditionNotMetException.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/PreconditionNotMetException.java
rename to app/src/main/java/im/conversations/android/xmpp/PreconditionNotMetException.java
diff --git a/src/main/java/im/conversations/android/xmpp/PubSubErrorException.java b/app/src/main/java/im/conversations/android/xmpp/PubSubErrorException.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/PubSubErrorException.java
rename to app/src/main/java/im/conversations/android/xmpp/PubSubErrorException.java
diff --git a/src/main/java/im/conversations/android/xmpp/ServiceDescription.java b/app/src/main/java/im/conversations/android/xmpp/ServiceDescription.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/ServiceDescription.java
rename to app/src/main/java/im/conversations/android/xmpp/ServiceDescription.java
diff --git a/src/main/java/im/conversations/android/xmpp/Timestamps.java b/app/src/main/java/im/conversations/android/xmpp/Timestamps.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/Timestamps.java
rename to app/src/main/java/im/conversations/android/xmpp/Timestamps.java
diff --git a/src/main/java/im/conversations/android/xmpp/XmppConnection.java b/app/src/main/java/im/conversations/android/xmpp/XmppConnection.java
similarity index 89%
rename from src/main/java/im/conversations/android/xmpp/XmppConnection.java
rename to app/src/main/java/im/conversations/android/xmpp/XmppConnection.java
index 17a7296ef..fe6f46d4d 100644
--- a/src/main/java/im/conversations/android/xmpp/XmppConnection.java
+++ b/app/src/main/java/im/conversations/android/xmpp/XmppConnection.java
@@ -5,7 +5,6 @@ import android.os.Build;
import android.os.SystemClock;
import android.security.KeyChain;
import android.util.Base64;
-import android.util.Log;
import android.util.Pair;
import android.util.SparseArray;
import androidx.annotation.NonNull;
@@ -18,15 +17,7 @@ import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.SettableFuture;
-import eu.siacs.conversations.Config;
-import eu.siacs.conversations.R;
-import eu.siacs.conversations.services.MemorizingTrustManager;
-import eu.siacs.conversations.services.NotificationService;
-import eu.siacs.conversations.ui.util.PendingItem;
-import eu.siacs.conversations.utils.PhoneHelper;
-import eu.siacs.conversations.utils.Resolver;
-import eu.siacs.conversations.utils.SocksSocketFactory;
-import eu.siacs.conversations.xmpp.bind.Bind2;
+import im.conversations.android.BuildConfig;
import im.conversations.android.Conversations;
import im.conversations.android.IDs;
import im.conversations.android.database.ConversationsDatabase;
@@ -34,8 +25,12 @@ import im.conversations.android.database.CredentialStore;
import im.conversations.android.database.model.Account;
import im.conversations.android.database.model.Connection;
import im.conversations.android.database.model.Credential;
+import im.conversations.android.dns.Resolver;
+import im.conversations.android.socks.SocksSocketFactory;
import im.conversations.android.tls.SSLSockets;
+import im.conversations.android.tls.TrustManagers;
import im.conversations.android.tls.XmppDomainVerifier;
+import im.conversations.android.util.PendingItem;
import im.conversations.android.xml.Element;
import im.conversations.android.xml.Namespace;
import im.conversations.android.xml.Tag;
@@ -46,11 +41,13 @@ import im.conversations.android.xmpp.manager.CarbonsManager;
import im.conversations.android.xmpp.manager.DiscoManager;
import im.conversations.android.xmpp.model.Extension;
import im.conversations.android.xmpp.model.StreamElement;
+import im.conversations.android.xmpp.model.bind2.BindInlineFeatures;
import im.conversations.android.xmpp.model.csi.Active;
import im.conversations.android.xmpp.model.csi.Inactive;
import im.conversations.android.xmpp.model.error.Condition;
import im.conversations.android.xmpp.model.error.Error;
import im.conversations.android.xmpp.model.ping.Ping;
+import im.conversations.android.xmpp.model.sasl2.Inline;
import im.conversations.android.xmpp.model.sm.Ack;
import im.conversations.android.xmpp.model.sm.Enable;
import im.conversations.android.xmpp.model.sm.Request;
@@ -82,7 +79,6 @@ import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
-import java.util.Collections;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
@@ -113,6 +109,9 @@ public class XmppConnection implements Runnable {
private static final Logger LOGGER = LoggerFactory.getLogger(XmppConnection.class);
+ private static final boolean EXTENDED_SM_LOGGING = false;
+ private static final int CONNECT_DISCO_TIMEOUT = 20;
+
protected final Account account;
private final SparseArray mStanzaQueue = new SparseArray<>();
private final Hashtable>> packetCallbacks = new Hashtable<>();
@@ -185,8 +184,7 @@ public class XmppConnection implements Runnable {
if (Strings.isNullOrEmpty(resource)) {
return null;
}
- int fixedPartLength =
- context.getString(R.string.app_name).length() + 1; // include the trailing dot
+ int fixedPartLength = BuildConfig.APP_NAME.length() + 1; // include the trailing dot
int randomPartLength = 4; // 3 bytes
if (resource.length() > fixedPartLength + randomPartLength) {
if (validBase64(
@@ -208,8 +206,7 @@ public class XmppConnection implements Runnable {
private void changeStatus(final ConnectionState nextStatus) {
synchronized (this) {
if (Thread.currentThread().isInterrupted()) {
- Log.d(
- Config.LOGTAG,
+ LOGGER.debug(
account.address
+ ": not changing status to "
+ nextStatus
@@ -268,7 +265,7 @@ public class XmppConnection implements Runnable {
ConversationsDatabase.getInstance(context)
.accountDao()
.getConnectionSettings(account.id);
- Log.d(Config.LOGTAG, account.address + ": connecting");
+ LOGGER.debug(account.address + ": connecting");
this.encryptionEnabled = false;
this.inSmacksSession = false;
this.quickStartInProgress = false;
@@ -301,8 +298,7 @@ public class XmppConnection implements Runnable {
directTls = connection.directTls;
}
- Log.d(
- Config.LOGTAG,
+ LOGGER.debug(
account.address
+ ": connect to "
+ destination
@@ -318,8 +314,7 @@ public class XmppConnection implements Runnable {
try {
startXmpp(localSocket);
} catch (final InterruptedException e) {
- Log.d(
- Config.LOGTAG,
+ LOGGER.debug(
account.address + ": thread was interrupted before beginning stream");
return;
} catch (final Exception e) {
@@ -334,11 +329,11 @@ public class XmppConnection implements Runnable {
results = Resolver.resolve(domain);
}
if (Thread.currentThread().isInterrupted()) {
- Log.d(Config.LOGTAG, account.address + ": Thread was interrupted");
+ LOGGER.debug(account.address + ": Thread was interrupted");
return;
}
if (results.size() == 0) {
- Log.e(Config.LOGTAG, account.address + ": Resolver results were empty");
+ LOGGER.warn("Resolver results were empty");
return;
}
final Resolver.Result storedBackupResult;
@@ -350,8 +345,7 @@ public class XmppConnection implements Runnable {
null; // context.databaseBackend.findResolverResult(domain);
if (storedBackupResult != null && !results.contains(storedBackupResult)) {
results.add(storedBackupResult);
- Log.d(
- Config.LOGTAG,
+ LOGGER.debug(
account.address
+ ": loaded backup resolver result from db: "
+ storedBackupResult);
@@ -361,7 +355,7 @@ public class XmppConnection implements Runnable {
iterator.hasNext(); ) {
final Resolver.Result result = iterator.next();
if (Thread.currentThread().isInterrupted()) {
- Log.d(Config.LOGTAG, account.address + ": Thread was interrupted");
+ LOGGER.debug(account.address + ": Thread was interrupted");
return;
}
try {
@@ -369,12 +363,11 @@ public class XmppConnection implements Runnable {
this.encryptionEnabled = result.isDirectTls();
verifiedHostname =
result.isAuthenticated() ? result.getHostname().toString() : null;
- Log.d(Config.LOGTAG, "verified hostname " + verifiedHostname);
+ LOGGER.debug("verified hostname " + verifiedHostname);
final InetSocketAddress addr;
if (result.getIp() != null) {
addr = new InetSocketAddress(result.getIp(), result.getPort());
- Log.d(
- Config.LOGTAG,
+ LOGGER.debug(
account.address
+ ": using values from resolver "
+ (result.getHostname() == null
@@ -390,8 +383,7 @@ public class XmppConnection implements Runnable {
new InetSocketAddress(
IDN.toASCII(result.getHostname().toString()),
result.getPort());
- Log.d(
- Config.LOGTAG,
+ LOGGER.debug(
account.address
+ ": using values from resolver "
+ result.getHostname().toString()
@@ -402,13 +394,13 @@ public class XmppConnection implements Runnable {
}
localSocket = new Socket();
- localSocket.connect(addr, Config.SOCKET_TIMEOUT * 1000);
+ localSocket.connect(addr, ConnectionPool.SOCKET_TIMEOUT * 1000);
if (this.encryptionEnabled) {
localSocket = upgradeSocketToTls(localSocket);
}
- localSocket.setSoTimeout(Config.SOCKET_TIMEOUT * 1000);
+ localSocket.setSoTimeout(ConnectionPool.SOCKET_TIMEOUT * 1000);
if (startXmpp(localSocket)) {
localSocket.setSoTimeout(
0); // reset to 0; once the connection is established we don’t
@@ -427,14 +419,12 @@ public class XmppConnection implements Runnable {
throw e;
}
} catch (InterruptedException e) {
- Log.d(
- Config.LOGTAG,
+ LOGGER.debug(
account.address
+ ": thread was interrupted before beginning stream");
return;
} catch (final Throwable e) {
- Log.d(
- Config.LOGTAG,
+ LOGGER.debug(
account.address
+ ": "
+ e.getMessage()
@@ -459,15 +449,14 @@ public class XmppConnection implements Runnable {
} catch (final SocksSocketFactory.SocksProxyNotFoundException e) {
this.changeStatus(ConnectionState.TOR_NOT_AVAILABLE);
} catch (final IOException | XmlPullParserException e) {
- Log.d(Config.LOGTAG, account.address + ": " + e.getMessage());
+ LOGGER.debug(account.address + ": " + e.getMessage());
this.changeStatus(ConnectionState.OFFLINE);
this.attempt = Math.max(0, this.attempt - 1);
} finally {
if (!Thread.currentThread().isInterrupted()) {
forceCloseSocket();
} else {
- Log.d(
- Config.LOGTAG,
+ LOGGER.debug(
account.address
+ ": not force closing socket because thread was interrupted");
}
@@ -514,7 +503,6 @@ public class XmppConnection implements Runnable {
private SSLSocketFactory getSSLSocketFactory()
throws NoSuchAlgorithmException, KeyManagementException {
final SSLContext sc = SSLSockets.getSSLContext();
- final MemorizingTrustManager trustManager = MemorizingTrustManager.create(context);
final KeyManager[] keyManager;
final Credential credential = CredentialStore.getInstance(context).get(account);
if (Strings.isNullOrEmpty(credential.privateKeyAlias)) {
@@ -528,7 +516,7 @@ public class XmppConnection implements Runnable {
// we need a better solution for this using live data or similar
sc.init(
keyManager,
- new X509TrustManager[] {trustManager.getInteractive(domain)},
+ new X509TrustManager[] {TrustManagers.getTrustManager()},
Conversations.SECURE_RANDOM);
return sc.getSocketFactory();
}
@@ -538,9 +526,7 @@ public class XmppConnection implements Runnable {
synchronized (this) {
this.mThread = Thread.currentThread();
if (this.mThread.isInterrupted()) {
- Log.d(
- Config.LOGTAG,
- account.address + ": aborting connect because thread was interrupted");
+ LOGGER.debug(account.address + ": aborting connect because thread was interrupted");
return;
}
forceCloseSocket();
@@ -578,8 +564,7 @@ public class XmppConnection implements Runnable {
final Element challenge = tagReader.readElement(nextTag);
processChallenge(challenge);
} else {
- Log.d(
- Config.LOGTAG,
+ LOGGER.debug(
account.address + ": received 'challenge on an unsecure connection");
throw new StateChangingException(ConnectionState.INCOMPATIBLE_CLIENT);
}
@@ -591,30 +576,26 @@ public class XmppConnection implements Runnable {
processResumed(resumed);
} else if (nextTag.isStart("r")) {
tagReader.readElement(nextTag);
- if (Config.EXTENDED_SM_LOGGING) {
- Log.d(
- Config.LOGTAG,
+ if (EXTENDED_SM_LOGGING) {
+ LOGGER.debug(
account.address + ": acknowledging stanza #" + this.stanzasReceived);
}
final Ack ack = new Ack(this.stanzasReceived);
tagWriter.writeStanzaAsync(ack);
} else if (nextTag.isStart("a")) {
- synchronized (NotificationService.CATCHUP_LOCK) {
- if (mWaitingForSmCatchup.compareAndSet(true, false)) {
- final int messageCount = mSmCatchupMessageCounter.get();
- final int pendingIQs = packetCallbacks.size();
- Log.d(
- Config.LOGTAG,
- account.address
- + ": SM catchup complete (messages="
- + messageCount
- + ", pending IQs="
- + pendingIQs
- + ")");
- if (messageCount > 0) {
- // TODO finish notification backlog (ok to pling now)
- // context.getNotificationService().finishBacklog(true, account);
- }
+ if (mWaitingForSmCatchup.compareAndSet(true, false)) {
+ final int messageCount = mSmCatchupMessageCounter.get();
+ final int pendingIQs = packetCallbacks.size();
+ LOGGER.debug(
+ account.address
+ + ": SM catchup complete (messages="
+ + messageCount
+ + ", pending IQs="
+ + pendingIQs
+ + ")");
+ if (messageCount > 0) {
+ // TODO finish notification backlog (ok to pling now)
+ // context.getNotificationService().finishBacklog(true, account);
}
}
final Element ack = tagReader.readElement(nextTag);
@@ -626,9 +607,7 @@ public class XmppConnection implements Runnable {
acknowledgedMessages = acknowledgeStanzaUpTo(serverSequence.get());
} else {
acknowledgedMessages = false;
- Log.d(
- Config.LOGTAG,
- account.address + ": server send ack without sequence number");
+ LOGGER.debug(account.address + ": server send ack without sequence number");
}
}
} else if (nextTag.isStart("failed")) {
@@ -668,7 +647,7 @@ public class XmppConnection implements Runnable {
saslMechanism.getResponse(challenge.getContent(), sslSocketOrNull(socket)));
} catch (final SaslMechanism.AuthenticationException e) {
// TODO: Send auth abort tag.
- Log.e(Config.LOGTAG, e.toString());
+ LOGGER.error("Authentication failed", e);
throw new StateChangingException(ConnectionState.UNAUTHORIZED);
}
tagWriter.writeElement(response);
@@ -697,16 +676,16 @@ public class XmppConnection implements Runnable {
try {
currentSaslMechanism.getResponse(challenge, sslSocketOrNull(socket));
} catch (final SaslMechanism.AuthenticationException e) {
- Log.e(Config.LOGTAG, String.valueOf(e));
+ LOGGER.error("Authentication failed", e);
throw new StateChangingException(ConnectionState.UNAUTHORIZED);
}
- Log.d(Config.LOGTAG, account.address + ": logged in (using " + version + ")");
+ LOGGER.debug(account.address + ": logged in (using " + version + ")");
if (SaslMechanism.pin(currentSaslMechanism)) {
try {
CredentialStore.getInstance(context)
.setPinnedMechanism(account, currentSaslMechanism);
} catch (final Exception e) {
- Log.d(Config.LOGTAG, "unable to pin mechanism in credential store", e);
+ LOGGER.debug("unable to pin mechanism in credential store", e);
}
}
if (version == SaslMechanism.Version.SASL_2) {
@@ -719,8 +698,7 @@ public class XmppConnection implements Runnable {
? null
: JidCreate.from(authorizationIdentifier);
} catch (final XmppStringprepException e) {
- Log.d(
- Config.LOGTAG,
+ LOGGER.debug(
account.address
+ ": SASL 2.0 authorization identifier was not a valid jid");
throw new StateChangingException(ConnectionState.BIND_FAILURE);
@@ -728,14 +706,12 @@ public class XmppConnection implements Runnable {
if (authorizationJid == null) {
throw new StateChangingException(ConnectionState.BIND_FAILURE);
}
- Log.d(
- Config.LOGTAG,
+ LOGGER.debug(
account.address
+ ": SASL 2.0 authorization identifier was "
+ authorizationJid);
if (!account.address.getDomain().equals(authorizationJid.getDomain())) {
- Log.d(
- Config.LOGTAG,
+ LOGGER.debug(
account.address
+ ": server tried to re-assign domain to "
+ authorizationJid.getDomain());
@@ -748,9 +724,7 @@ public class XmppConnection implements Runnable {
final Element tokenWrapper = success.findChild("token", Namespace.FAST);
final String token = tokenWrapper == null ? null : tokenWrapper.getAttribute("token");
if (bound != null && resumed != null) {
- Log.d(
- Config.LOGTAG,
- account.address + ": server sent bound and resumed in SASL2 success");
+ LOGGER.debug(account.address + ": server sent bound and resumed in SASL2 success");
throw new StateChangingException(ConnectionState.INCOMPATIBLE_SERVER);
}
final boolean processNopStreamFeatures;
@@ -773,7 +747,7 @@ public class XmppConnection implements Runnable {
enableStreamManagement();
}
if (carbonsEnabled != null) {
- Log.d(Config.LOGTAG, account.address + ": successfully enabled carbons");
+ LOGGER.debug(account.address + ": successfully enabled carbons");
}
sendPostBindInitialization(carbonsEnabled != null);
processNopStreamFeatures = true;
@@ -792,15 +766,12 @@ public class XmppConnection implements Runnable {
try {
CredentialStore.getInstance(context)
.setFastToken(account, tokenMechanism, token);
- Log.d(
- Config.LOGTAG,
- account.address + ": storing hashed token " + tokenMechanism);
+ LOGGER.debug(account.address + ": storing hashed token " + tokenMechanism);
} catch (final Exception e) {
- Log.d(Config.LOGTAG, "could not store fast token", e);
+ LOGGER.debug("could not store fast token", e);
}
} else if (this.hashTokenRequest != null) {
- Log.w(
- Config.LOGTAG,
+ LOGGER.warn(
account.address
+ ": no response to our hashed token request "
+ this.hashTokenRequest);
@@ -829,9 +800,8 @@ public class XmppConnection implements Runnable {
private void resetOutboundStanzaQueue() {
synchronized (this.mStanzaQueue) {
final List intermediateStanzas = new ArrayList<>();
- if (Config.EXTENDED_SM_LOGGING) {
- Log.d(
- Config.LOGTAG,
+ if (EXTENDED_SM_LOGGING) {
+ LOGGER.debug(
account.address
+ ": stanzas sent before auth: "
+ this.stanzasSentBeforeAuthentication);
@@ -847,9 +817,8 @@ public class XmppConnection implements Runnable {
this.mStanzaQueue.put(i, intermediateStanzas.get(i));
}
this.stanzasSent = intermediateStanzas.size();
- if (Config.EXTENDED_SM_LOGGING) {
- Log.d(
- Config.LOGTAG,
+ if (EXTENDED_SM_LOGGING) {
+ LOGGER.debug(
account.address
+ ": resetting outbound stanza queue to "
+ this.stanzasSent);
@@ -865,9 +834,8 @@ public class XmppConnection implements Runnable {
"Processed NOP stream features after success {}",
this.streamFeatures.getExtensionIds());
} else {
- Log.d(Config.LOGTAG, account.address + ": received " + tag);
- Log.d(
- Config.LOGTAG,
+ LOGGER.debug(account.address + ": received " + tag);
+ LOGGER.debug(
account.address + ": server did not send stream features after SASL2 success");
throw new StateChangingException(ConnectionState.INCOMPATIBLE_SERVER);
}
@@ -880,22 +848,21 @@ public class XmppConnection implements Runnable {
} catch (final IllegalArgumentException e) {
throw new StateChangingException(ConnectionState.INCOMPATIBLE_SERVER);
}
- Log.d(Config.LOGTAG, failure.toString());
- Log.d(Config.LOGTAG, account.address + ": login failure " + version);
+ LOGGER.debug(failure.toString());
+ LOGGER.debug(account.address + ": login failure " + version);
if (SaslMechanism.hashedToken(this.saslMechanism)) {
- Log.d(Config.LOGTAG, account.address + ": resetting token");
+ LOGGER.debug(account.address + ": resetting token");
try {
CredentialStore.getInstance(context).resetFastToken(account);
} catch (final Exception e) {
- Log.d(Config.LOGTAG, "could not reset fast token in credential store", e);
+ LOGGER.debug("could not reset fast token in credential store", e);
}
}
if (failure.hasChild("temporary-auth-failure")) {
throw new StateChangingException(ConnectionState.TEMPORARY_AUTH_FAILURE);
}
if (SaslMechanism.hashedToken(this.saslMechanism)) {
- Log.d(
- Config.LOGTAG,
+ LOGGER.debug(
account.address
+ ": fast authentication failed. falling back to regular"
+ " authentication");
@@ -917,9 +884,9 @@ public class XmppConnection implements Runnable {
final String streamId;
if (enabled.getAttributeAsBoolean("resume")) {
streamId = enabled.getAttribute("id");
- Log.d(Config.LOGTAG, account.address + ": stream management enabled (resumable)");
+ LOGGER.debug(account.address + ": stream management enabled (resumable)");
} else {
- Log.d(Config.LOGTAG, account.address + ": stream management enabled");
+ LOGGER.debug(account.address + ": stream management enabled");
streamId = null;
}
this.streamId = streamId;
@@ -946,10 +913,10 @@ public class XmppConnection implements Runnable {
final boolean acknowledgedMessages;
synchronized (this.mStanzaQueue) {
if (serverCount < stanzasSent) {
- Log.d(Config.LOGTAG, account.address + ": session resumed with lost packages");
+ LOGGER.debug(account.address + ": session resumed with lost packages");
stanzasSent = serverCount;
} else {
- Log.d(Config.LOGTAG, account.address + ": session resumed");
+ LOGGER.debug(account.address + ": session resumed");
}
acknowledgedMessages = acknowledgeStanzaUpTo(serverCount);
for (int i = 0; i < this.mStanzaQueue.size(); ++i) {
@@ -957,7 +924,7 @@ public class XmppConnection implements Runnable {
}
mStanzaQueue.clear();
}
- Log.d(Config.LOGTAG, account.address + ": resending " + failedStanzas.size() + " stanzas");
+ LOGGER.debug(account.address + ": resending " + failedStanzas.size() + " stanzas");
for (final Stanza packet : failedStanzas) {
if (packet instanceof Message) {
Message message = (Message) packet;
@@ -971,8 +938,7 @@ public class XmppConnection implements Runnable {
}
private void changeStatusToOnline() {
- Log.d(
- Config.LOGTAG,
+ LOGGER.debug(
account.address
+ ": online with resource "
+ connectionAddress.getResourceOrNull());
@@ -982,8 +948,7 @@ public class XmppConnection implements Runnable {
private void processFailed(final Element failed, final boolean sendBindRequest) {
final Optional serverCount = failed.getOptionalIntAttribute("h");
if (serverCount.isPresent()) {
- Log.d(
- Config.LOGTAG,
+ LOGGER.debug(
account.address
+ ": resumption failed but server acknowledged stanza #"
+ serverCount.get());
@@ -992,7 +957,7 @@ public class XmppConnection implements Runnable {
acknowledgedMessages = acknowledgeStanzaUpTo(serverCount.get());
}
} else {
- Log.d(Config.LOGTAG, account.address + ": resumption failed");
+ LOGGER.debug(account.address + ": resumption failed");
}
resetStreamId();
if (sendBindRequest) {
@@ -1002,8 +967,7 @@ public class XmppConnection implements Runnable {
private boolean acknowledgeStanzaUpTo(final int serverCount) {
if (serverCount > stanzasSent) {
- Log.e(
- Config.LOGTAG,
+ LOGGER.error(
"server acknowledged more stanzas than we sent. serverCount="
+ serverCount
+ ", ourCount="
@@ -1012,9 +976,8 @@ public class XmppConnection implements Runnable {
boolean acknowledgedMessages = false;
for (int i = 0; i < mStanzaQueue.size(); ++i) {
if (serverCount >= mStanzaQueue.keyAt(i)) {
- if (Config.EXTENDED_SM_LOGGING) {
- Log.d(
- Config.LOGTAG,
+ if (EXTENDED_SM_LOGGING) {
+ LOGGER.debug(
account.address
+ ": server acknowledged stanza #"
+ mStanzaQueue.keyAt(i));
@@ -1045,8 +1008,7 @@ public class XmppConnection implements Runnable {
if (inSmacksSession) {
++stanzasReceived;
} else if (this.streamFeatures.streamManagement()) {
- Log.d(
- Config.LOGTAG,
+ LOGGER.debug(
account.address
+ ": not counting stanza("
+ stanza.getClass().getSimpleName()
@@ -1070,7 +1032,7 @@ public class XmppConnection implements Runnable {
packetCallbacks.remove(packet.getId());
} else {
callback = null;
- Log.e(Config.LOGTAG, account.address + ": ignoring spoofed iq packet");
+ LOGGER.warn("Ignoring spoofed iq stanza");
}
} else {
if (packet.getFrom() != null
@@ -1079,7 +1041,7 @@ public class XmppConnection implements Runnable {
packetCallbacks.remove(packet.getId());
} else {
callback = null;
- Log.e(Config.LOGTAG, account.address + ": ignoring spoofed iq packet");
+ LOGGER.error(account.address + ": ignoring spoofed iq packet");
}
}
} else if (packet.getType() == Iq.Type.GET || packet.getType() == Iq.Type.SET) {
@@ -1169,9 +1131,7 @@ public class XmppConnection implements Runnable {
account.address.getDomain().toString(),
this.verifiedHostname,
sslSocket.getSession())) {
- Log.d(
- Config.LOGTAG,
- account.address + ": TLS certificate domain verification failed");
+ LOGGER.debug(account.address + ": TLS certificate domain verification failed");
Closables.close(sslSocket);
throw new StateChangingException(ConnectionState.TLS_ERROR_DOMAIN);
}
@@ -1197,17 +1157,14 @@ public class XmppConnection implements Runnable {
}
if (isFastTokenAvailable(
this.streamFeatures.findChild("authentication", Namespace.SASL_2))) {
- Log.d(
- Config.LOGTAG,
- account.address + ": fast token available; resetting quick start");
+ LOGGER.debug(account.address + ": fast token available; resetting quick start");
ConversationsDatabase.getInstance(context)
.accountDao()
.setQuickStartAvailable(account.id, false);
}
return;
}
- Log.d(
- Config.LOGTAG,
+ LOGGER.debug(
account.address + ": server lost support for SASL 2. quick start not possible");
ConversationsDatabase.getInstance(context)
.accountDao()
@@ -1234,10 +1191,8 @@ public class XmppConnection implements Runnable {
&& shouldAuthenticate) {
authenticate(SaslMechanism.Version.SASL);
} else if (this.streamFeatures.streamManagement() && streamId != null && !inSmacksSession) {
- if (Config.EXTENDED_SM_LOGGING) {
- Log.d(
- Config.LOGTAG,
- account.address + ": resuming after stanza #" + stanzasReceived);
+ if (EXTENDED_SM_LOGGING) {
+ LOGGER.debug(account.address + ": resuming after stanza #" + stanzasReceived);
}
final Resume resume = new Resume(this.streamId, stanzasReceived);
this.mSmCatchupMessageCounter.set(0);
@@ -1269,7 +1224,7 @@ public class XmppConnection implements Runnable {
}
private boolean isSecure() {
- return this.encryptionEnabled || Config.ALLOW_NON_TLS_CONNECTIONS || account.isOnion();
+ return this.encryptionEnabled || account.isOnion();
}
private void authenticate(final SaslMechanism.Version version) throws IOException {
@@ -1302,7 +1257,7 @@ public class XmppConnection implements Runnable {
}
quickStartAvailable = false;
} else if (version == SaslMechanism.Version.SASL_2) {
- final Element inline = authElement.findChild("inline", Namespace.SASL_2);
+ final Inline inline = authElement.getExtension(Inline.class);
final boolean sm = inline != null && inline.hasChild("sm", Namespace.STREAM_MANAGEMENT);
final HashedToken.Mechanism hashTokenRequest;
if (usingFast) {
@@ -1314,13 +1269,11 @@ public class XmppConnection implements Runnable {
hashTokenRequest =
HashedToken.Mechanism.best(fastMechanisms, SSLSockets.version(this.socket));
}
- // TODO fix me. properly parse bind2 features
- final Collection bindFeatures =
- Collections.emptyList(); // Bind2.features(inline);
+ final Collection bindFeatures = BindInlineFeatures.get(inline);
quickStartAvailable =
sm
&& bindFeatures != null
- && bindFeatures.containsAll(Bind2.QUICKSTART_FEATURES);
+ && bindFeatures.containsAll(BindInlineFeatures.QUICKSTART_FEATURES);
this.hashTokenRequest = hashTokenRequest;
authenticate =
generateAuthenticationRequest(
@@ -1333,8 +1286,7 @@ public class XmppConnection implements Runnable {
.accountDao()
.setQuickStartAvailable(account.id, quickStartAvailable);
- Log.d(
- Config.LOGTAG,
+ LOGGER.debug(
account.address
+ ": Authenticating with "
+ version
@@ -1357,8 +1309,7 @@ public class XmppConnection implements Runnable {
final @Nullable SaslMechanism saslMechanism, Collection mechanisms)
throws StateChangingException {
if (saslMechanism == null) {
- Log.d(
- Config.LOGTAG,
+ LOGGER.debug(
account.address + ": unable to find supported SASL mechanism in " + mechanisms);
throw new StateChangingException(ConnectionState.INCOMPATIBLE_SERVER);
}
@@ -1370,8 +1321,7 @@ public class XmppConnection implements Runnable {
account, CredentialStore.getInstance(context).get(account));
final int pinnedMechanism = saslFactory.getPinnedMechanismPriority();
if (pinnedMechanism > saslMechanism.getPriority()) {
- Log.e(
- Config.LOGTAG,
+ LOGGER.error(
"Auth failed. Authentication mechanism "
+ saslMechanism.getMechanism()
+ " has lower priority ("
@@ -1387,7 +1337,7 @@ public class XmppConnection implements Runnable {
private Element generateAuthenticationRequest(
final String firstMessage, final boolean usingFast) {
return generateAuthenticationRequest(
- firstMessage, usingFast, null, Bind2.QUICKSTART_FEATURES, true);
+ firstMessage, usingFast, null, BindInlineFeatures.QUICKSTART_FEATURES, true);
}
private Element generateAuthenticationRequest(
@@ -1402,12 +1352,10 @@ public class XmppConnection implements Runnable {
}
final Element userAgent = authenticate.addChild("user-agent");
userAgent.setAttribute("id", account.getPublicDeviceId().toString());
- userAgent.addChild("software").setContent(context.getString(R.string.app_name));
- if (!PhoneHelper.isEmulator()) {
- userAgent
- .addChild("device")
- .setContent(String.format("%s %s", Build.MANUFACTURER, Build.MODEL));
- }
+ userAgent.addChild("software").setContent(BuildConfig.APP_NAME);
+ userAgent
+ .addChild("device")
+ .setContent(String.format("%s %s", Build.MANUFACTURER, Build.MODEL));
if (bind != null) {
authenticate.addChild(generateBindRequest(bind));
}
@@ -1428,9 +1376,9 @@ public class XmppConnection implements Runnable {
}
private Element generateBindRequest(final Collection bindFeatures) {
- Log.d(Config.LOGTAG, "inline bind features: " + bindFeatures);
+ LOGGER.debug("inline bind features: " + bindFeatures);
final Element bind = new Element("bind", Namespace.BIND2);
- bind.addChild("tag").setContent(context.getString(R.string.app_name));
+ bind.addChild("tag").setContent(BuildConfig.APP_NAME);
if (bindFeatures.contains(Namespace.CARBONS)) {
bind.addChild("enable", Namespace.CARBONS);
}
@@ -1496,8 +1444,7 @@ public class XmppConnection implements Runnable {
try {
assignedJid = JidCreate.from(jid);
} catch (final XmppStringprepException e) {
- Log.d(
- Config.LOGTAG,
+ LOGGER.debug(
account.address
+ ": server reported invalid jid ("
+ jid
@@ -1506,8 +1453,7 @@ public class XmppConnection implements Runnable {
}
if (!account.address.getDomain().equals(assignedJid.getDomain())) {
- Log.d(
- Config.LOGTAG,
+ LOGGER.debug(
account.address
+ ": server tried to re-assign domain to "
+ assignedJid.getDomain());
@@ -1523,8 +1469,7 @@ public class XmppConnection implements Runnable {
}
return;
} else {
- Log.d(
- Config.LOGTAG,
+ LOGGER.debug(
account.address
+ ": disconnecting because of bind failure ("
+ packet);
@@ -1537,8 +1482,7 @@ public class XmppConnection implements Runnable {
ConversationsDatabase.getInstance(context)
.accountDao()
.setResource(account.id, alternativeResource);
- Log.d(
- Config.LOGTAG,
+ LOGGER.debug(
account.address
+ ": switching resource due to conflict ("
+ alternativeResource
@@ -1559,8 +1503,7 @@ public class XmppConnection implements Runnable {
if (this.packetCallbacks.size() == 0) {
return;
}
- Log.d(
- Config.LOGTAG,
+ LOGGER.debug(
account.address
+ ": clearing "
+ this.packetCallbacks.size()
@@ -1577,8 +1520,7 @@ public class XmppConnection implements Runnable {
try {
callback.accept(failurePacket);
} catch (StateChangingError error) {
- Log.d(
- Config.LOGTAG,
+ LOGGER.debug(
account.address
+ ": caught StateChangingError("
+ error.state.toString()
@@ -1586,8 +1528,7 @@ public class XmppConnection implements Runnable {
// ignore
}
}
- Log.d(
- Config.LOGTAG,
+ LOGGER.debug(
account.address
+ ": done clearing iq callbacks. "
+ this.packetCallbacks.size()
@@ -1595,7 +1536,7 @@ public class XmppConnection implements Runnable {
}
private void sendStartSession() {
- Log.d(Config.LOGTAG, account.address + ": sending legacy session to outdated server");
+ LOGGER.debug(account.address + ": sending legacy session to outdated server");
final Iq startSession = new Iq(Iq.Type.SET);
startSession.addChild("session", "urn:ietf:params:xml:ns:xmpp-session");
this.sendIqPacketUnbound(
@@ -1628,7 +1569,7 @@ public class XmppConnection implements Runnable {
private void sendPostBindInitialization(final boolean carbonsEnabled) {
getManager(CarbonsManager.class).setEnabled(carbonsEnabled);
- Log.d(Config.LOGTAG, account.address + ": starting service discovery");
+ LOGGER.debug(account.address + ": starting service discovery");
final ArrayList> discoFutures = new ArrayList<>();
final var discoManager = getManager(DiscoManager.class);
@@ -1646,7 +1587,7 @@ public class XmppConnection implements Runnable {
final var discoFuture =
Futures.withTimeout(
Futures.allAsList(discoFutures),
- Config.CONNECT_DISCO_TIMEOUT,
+ CONNECT_DISCO_TIMEOUT,
TimeUnit.SECONDS,
ConnectionPool.CONNECTION_SCHEDULER);
@@ -1661,7 +1602,7 @@ public class XmppConnection implements Runnable {
@Override
public void onFailure(@NonNull Throwable t) {
- Log.d(Config.LOGTAG, "unable to fetch disco", t);
+ LOGGER.debug("unable to fetch disco", t);
// TODO reset stream ID so we get a proper connect next time
finalizeBind();
}
@@ -1700,8 +1641,7 @@ public class XmppConnection implements Runnable {
ConversationsDatabase.getInstance(context)
.accountDao()
.setResource(account.id, alternativeResource);
- Log.d(
- Config.LOGTAG,
+ LOGGER.debug(
account.address
+ ": switching resource due to conflict ("
+ alternativeResource
@@ -1712,11 +1652,11 @@ public class XmppConnection implements Runnable {
} else if (streamError.hasChild("policy-violation")) {
this.lastConnect = SystemClock.elapsedRealtime();
final String text = streamError.findChildContent("text");
- Log.d(Config.LOGTAG, account.address + ": policy violation. " + text);
+ LOGGER.debug(account.address + ": policy violation. " + text);
failPendingMessages(text);
throw new StateChangingException(ConnectionState.POLICY_VIOLATION);
} else {
- Log.d(Config.LOGTAG, account.address + ": stream error " + streamError);
+ LOGGER.debug(account.address + ": stream error " + streamError);
throw new StateChangingException(ConnectionState.STREAM_ERROR);
}
}
@@ -1747,7 +1687,6 @@ public class XmppConnection implements Runnable {
SaslMechanism.ensureAvailable(saslFactory.getQuickStartMechanism(), sslVersion);
final boolean secureConnection = sslVersion != SSLSockets.Version.NONE;
if (secureConnection
- && Config.QUICKSTART_ENABLED
&& quickStartMechanism != null
&& ConversationsDatabase.getInstance(context)
.accountDao()
@@ -1765,8 +1704,7 @@ public class XmppConnection implements Runnable {
this.stanzasSentBeforeAuthentication = this.stanzasSent;
tagWriter.writeElement(authenticate);
}
- Log.d(
- Config.LOGTAG,
+ LOGGER.debug(
account.address + ": quick start with " + quickStartMechanism.getMechanism());
return true;
} else {
@@ -1790,7 +1728,7 @@ public class XmppConnection implements Runnable {
}
private String createNewResource(final String postfixId) {
- return String.format("%s.%s", context.getString(R.string.app_name), postfixId);
+ return String.format("%s.%s", BuildConfig.APP_NAME, postfixId);
}
public ListenableFuture sendIqPacket(final Iq packet) {
@@ -1886,8 +1824,7 @@ public class XmppConnection implements Runnable {
if (sendToUnboundStream || isBound) {
tagWriter.writeStanzaAsync(packet);
} else {
- Log.d(
- Config.LOGTAG,
+ LOGGER.debug(
account.address
+ " do not write stanza to unbound stream "
+ packet.toString());
@@ -1903,9 +1840,8 @@ public class XmppConnection implements Runnable {
}
++stanzasSent;
- if (Config.EXTENDED_SM_LOGGING) {
- Log.d(
- Config.LOGTAG,
+ if (EXTENDED_SM_LOGGING) {
+ LOGGER.debug(
account.address
+ ": counting outbound "
+ packet.getName()
@@ -1914,9 +1850,8 @@ public class XmppConnection implements Runnable {
}
this.mStanzaQueue.append(stanzasSent, stanza);
if (stanza instanceof Message && stanza.getId() != null && inSmacksSession) {
- if (Config.EXTENDED_SM_LOGGING) {
- Log.d(
- Config.LOGTAG,
+ if (EXTENDED_SM_LOGGING) {
+ LOGGER.debug(
account.address
+ ": requesting ack for message stanza #"
+ stanzasSent);
@@ -1969,7 +1904,7 @@ public class XmppConnection implements Runnable {
public void disconnect(final boolean force) {
interrupt();
- Log.d(Config.LOGTAG, account.address + ": disconnecting force=" + force);
+ LOGGER.debug(account.address + ": disconnecting force=" + force);
if (force) {
forceCloseSocket();
} else {
@@ -1980,25 +1915,21 @@ public class XmppConnection implements Runnable {
final CountDownLatch streamCountDownLatch = this.mStreamCountDownLatch;
try {
currentTagWriter.await(1, TimeUnit.SECONDS);
- Log.d(Config.LOGTAG, account.address + ": closing stream");
+ LOGGER.debug(account.address + ": closing stream");
currentTagWriter.writeTag(Tag.end("stream:stream"));
if (streamCountDownLatch != null) {
if (streamCountDownLatch.await(1, TimeUnit.SECONDS)) {
- Log.d(Config.LOGTAG, account.address + ": remote ended stream");
+ LOGGER.debug(account.address + ": remote ended stream");
} else {
- Log.d(
- Config.LOGTAG,
+ LOGGER.debug(
account.address
+ ": remote has not closed socket. force closing");
}
}
} catch (InterruptedException e) {
- Log.d(
- Config.LOGTAG,
- account.address + ": interrupted while gracefully closing stream");
+ LOGGER.debug(account.address + ": interrupted while gracefully closing stream");
} catch (final IOException e) {
- Log.d(
- Config.LOGTAG,
+ LOGGER.debug(
account.address
+ ": io exception during disconnect ("
+ e.getMessage()
@@ -2118,11 +2049,11 @@ public class XmppConnection implements Runnable {
@Override
public X509Certificate[] getCertificateChain(String alias) {
- Log.d(Config.LOGTAG, "getting certificate chain");
+ LOGGER.debug("getting certificate chain");
try {
return KeyChain.getCertificateChain(context, alias);
} catch (final Exception e) {
- Log.d(Config.LOGTAG, "could not get certificate chain", e);
+ LOGGER.debug("could not get certificate chain", e);
return new X509Certificate[0];
}
}
diff --git a/src/main/java/im/conversations/android/xmpp/axolotl/AxolotlAddress.java b/app/src/main/java/im/conversations/android/xmpp/axolotl/AxolotlAddress.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/axolotl/AxolotlAddress.java
rename to app/src/main/java/im/conversations/android/xmpp/axolotl/AxolotlAddress.java
diff --git a/src/main/java/im/conversations/android/xmpp/manager/AbstractManager.java b/app/src/main/java/im/conversations/android/xmpp/manager/AbstractManager.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/manager/AbstractManager.java
rename to app/src/main/java/im/conversations/android/xmpp/manager/AbstractManager.java
diff --git a/src/main/java/im/conversations/android/xmpp/manager/ArchiveManager.java b/app/src/main/java/im/conversations/android/xmpp/manager/ArchiveManager.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/manager/ArchiveManager.java
rename to app/src/main/java/im/conversations/android/xmpp/manager/ArchiveManager.java
diff --git a/src/main/java/im/conversations/android/xmpp/manager/AvatarManager.java b/app/src/main/java/im/conversations/android/xmpp/manager/AvatarManager.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/manager/AvatarManager.java
rename to app/src/main/java/im/conversations/android/xmpp/manager/AvatarManager.java
diff --git a/src/main/java/im/conversations/android/xmpp/manager/AxolotlManager.java b/app/src/main/java/im/conversations/android/xmpp/manager/AxolotlManager.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/manager/AxolotlManager.java
rename to app/src/main/java/im/conversations/android/xmpp/manager/AxolotlManager.java
diff --git a/src/main/java/im/conversations/android/xmpp/manager/BlockingManager.java b/app/src/main/java/im/conversations/android/xmpp/manager/BlockingManager.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/manager/BlockingManager.java
rename to app/src/main/java/im/conversations/android/xmpp/manager/BlockingManager.java
diff --git a/src/main/java/im/conversations/android/xmpp/manager/BookmarkManager.java b/app/src/main/java/im/conversations/android/xmpp/manager/BookmarkManager.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/manager/BookmarkManager.java
rename to app/src/main/java/im/conversations/android/xmpp/manager/BookmarkManager.java
diff --git a/src/main/java/im/conversations/android/xmpp/manager/CarbonsManager.java b/app/src/main/java/im/conversations/android/xmpp/manager/CarbonsManager.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/manager/CarbonsManager.java
rename to app/src/main/java/im/conversations/android/xmpp/manager/CarbonsManager.java
diff --git a/src/main/java/im/conversations/android/xmpp/manager/ChatStateManager.java b/app/src/main/java/im/conversations/android/xmpp/manager/ChatStateManager.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/manager/ChatStateManager.java
rename to app/src/main/java/im/conversations/android/xmpp/manager/ChatStateManager.java
diff --git a/src/main/java/im/conversations/android/xmpp/manager/DiscoManager.java b/app/src/main/java/im/conversations/android/xmpp/manager/DiscoManager.java
similarity index 99%
rename from src/main/java/im/conversations/android/xmpp/manager/DiscoManager.java
rename to app/src/main/java/im/conversations/android/xmpp/manager/DiscoManager.java
index 50a910e9d..7e1756004 100644
--- a/src/main/java/im/conversations/android/xmpp/manager/DiscoManager.java
+++ b/app/src/main/java/im/conversations/android/xmpp/manager/DiscoManager.java
@@ -11,8 +11,8 @@ import com.google.common.io.BaseEncoding;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
-import eu.siacs.conversations.BuildConfig;
-import eu.siacs.conversations.R;
+import im.conversations.android.BuildConfig;
+import im.conversations.android.R;
import im.conversations.android.xml.Namespace;
import im.conversations.android.xmpp.Entity;
import im.conversations.android.xmpp.EntityCapabilities;
diff --git a/src/main/java/im/conversations/android/xmpp/manager/NickManager.java b/app/src/main/java/im/conversations/android/xmpp/manager/NickManager.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/manager/NickManager.java
rename to app/src/main/java/im/conversations/android/xmpp/manager/NickManager.java
diff --git a/src/main/java/im/conversations/android/xmpp/manager/PepManager.java b/app/src/main/java/im/conversations/android/xmpp/manager/PepManager.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/manager/PepManager.java
rename to app/src/main/java/im/conversations/android/xmpp/manager/PepManager.java
diff --git a/src/main/java/im/conversations/android/xmpp/manager/PresenceManager.java b/app/src/main/java/im/conversations/android/xmpp/manager/PresenceManager.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/manager/PresenceManager.java
rename to app/src/main/java/im/conversations/android/xmpp/manager/PresenceManager.java
diff --git a/src/main/java/im/conversations/android/xmpp/manager/PubSubManager.java b/app/src/main/java/im/conversations/android/xmpp/manager/PubSubManager.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/manager/PubSubManager.java
rename to app/src/main/java/im/conversations/android/xmpp/manager/PubSubManager.java
diff --git a/src/main/java/im/conversations/android/xmpp/manager/ReceiptManager.java b/app/src/main/java/im/conversations/android/xmpp/manager/ReceiptManager.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/manager/ReceiptManager.java
rename to app/src/main/java/im/conversations/android/xmpp/manager/ReceiptManager.java
diff --git a/src/main/java/im/conversations/android/xmpp/manager/RegistrationManager.java b/app/src/main/java/im/conversations/android/xmpp/manager/RegistrationManager.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/manager/RegistrationManager.java
rename to app/src/main/java/im/conversations/android/xmpp/manager/RegistrationManager.java
diff --git a/src/main/java/im/conversations/android/xmpp/manager/RosterManager.java b/app/src/main/java/im/conversations/android/xmpp/manager/RosterManager.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/manager/RosterManager.java
rename to app/src/main/java/im/conversations/android/xmpp/manager/RosterManager.java
diff --git a/src/main/java/im/conversations/android/xmpp/manager/StanzaIdManager.java b/app/src/main/java/im/conversations/android/xmpp/manager/StanzaIdManager.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/manager/StanzaIdManager.java
rename to app/src/main/java/im/conversations/android/xmpp/manager/StanzaIdManager.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/ByteContent.java b/app/src/main/java/im/conversations/android/xmpp/model/ByteContent.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/ByteContent.java
rename to app/src/main/java/im/conversations/android/xmpp/model/ByteContent.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/DeliveryReceipt.java b/app/src/main/java/im/conversations/android/xmpp/model/DeliveryReceipt.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/DeliveryReceipt.java
rename to app/src/main/java/im/conversations/android/xmpp/model/DeliveryReceipt.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/DeliveryReceiptRequest.java b/app/src/main/java/im/conversations/android/xmpp/model/DeliveryReceiptRequest.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/DeliveryReceiptRequest.java
rename to app/src/main/java/im/conversations/android/xmpp/model/DeliveryReceiptRequest.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/Extension.java b/app/src/main/java/im/conversations/android/xmpp/model/Extension.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/Extension.java
rename to app/src/main/java/im/conversations/android/xmpp/model/Extension.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/Hash.java b/app/src/main/java/im/conversations/android/xmpp/model/Hash.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/Hash.java
rename to app/src/main/java/im/conversations/android/xmpp/model/Hash.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/StreamElement.java b/app/src/main/java/im/conversations/android/xmpp/model/StreamElement.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/StreamElement.java
rename to app/src/main/java/im/conversations/android/xmpp/model/StreamElement.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/avatar/Data.java b/app/src/main/java/im/conversations/android/xmpp/model/avatar/Data.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/avatar/Data.java
rename to app/src/main/java/im/conversations/android/xmpp/model/avatar/Data.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/avatar/Info.java b/app/src/main/java/im/conversations/android/xmpp/model/avatar/Info.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/avatar/Info.java
rename to app/src/main/java/im/conversations/android/xmpp/model/avatar/Info.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/avatar/Metadata.java b/app/src/main/java/im/conversations/android/xmpp/model/avatar/Metadata.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/avatar/Metadata.java
rename to app/src/main/java/im/conversations/android/xmpp/model/avatar/Metadata.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/Bundle.java b/app/src/main/java/im/conversations/android/xmpp/model/axolotl/Bundle.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/axolotl/Bundle.java
rename to app/src/main/java/im/conversations/android/xmpp/model/axolotl/Bundle.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/Device.java b/app/src/main/java/im/conversations/android/xmpp/model/axolotl/Device.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/axolotl/Device.java
rename to app/src/main/java/im/conversations/android/xmpp/model/axolotl/Device.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/DeviceList.java b/app/src/main/java/im/conversations/android/xmpp/model/axolotl/DeviceList.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/axolotl/DeviceList.java
rename to app/src/main/java/im/conversations/android/xmpp/model/axolotl/DeviceList.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/ECPublicKeyContent.java b/app/src/main/java/im/conversations/android/xmpp/model/axolotl/ECPublicKeyContent.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/axolotl/ECPublicKeyContent.java
rename to app/src/main/java/im/conversations/android/xmpp/model/axolotl/ECPublicKeyContent.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/Encrypted.java b/app/src/main/java/im/conversations/android/xmpp/model/axolotl/Encrypted.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/axolotl/Encrypted.java
rename to app/src/main/java/im/conversations/android/xmpp/model/axolotl/Encrypted.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/Header.java b/app/src/main/java/im/conversations/android/xmpp/model/axolotl/Header.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/axolotl/Header.java
rename to app/src/main/java/im/conversations/android/xmpp/model/axolotl/Header.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/IdentityKey.java b/app/src/main/java/im/conversations/android/xmpp/model/axolotl/IdentityKey.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/axolotl/IdentityKey.java
rename to app/src/main/java/im/conversations/android/xmpp/model/axolotl/IdentityKey.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/Payload.java b/app/src/main/java/im/conversations/android/xmpp/model/axolotl/Payload.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/axolotl/Payload.java
rename to app/src/main/java/im/conversations/android/xmpp/model/axolotl/Payload.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/PreKey.java b/app/src/main/java/im/conversations/android/xmpp/model/axolotl/PreKey.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/axolotl/PreKey.java
rename to app/src/main/java/im/conversations/android/xmpp/model/axolotl/PreKey.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/PreKeys.java b/app/src/main/java/im/conversations/android/xmpp/model/axolotl/PreKeys.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/axolotl/PreKeys.java
rename to app/src/main/java/im/conversations/android/xmpp/model/axolotl/PreKeys.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/SignedPreKey.java b/app/src/main/java/im/conversations/android/xmpp/model/axolotl/SignedPreKey.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/axolotl/SignedPreKey.java
rename to app/src/main/java/im/conversations/android/xmpp/model/axolotl/SignedPreKey.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/SignedPreKeySignature.java b/app/src/main/java/im/conversations/android/xmpp/model/axolotl/SignedPreKeySignature.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/axolotl/SignedPreKeySignature.java
rename to app/src/main/java/im/conversations/android/xmpp/model/axolotl/SignedPreKeySignature.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/axolotl/package-info.java b/app/src/main/java/im/conversations/android/xmpp/model/axolotl/package-info.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/axolotl/package-info.java
rename to app/src/main/java/im/conversations/android/xmpp/model/axolotl/package-info.java
diff --git a/src/main/java/eu/siacs/conversations/xmpp/bind/Bind2.java b/app/src/main/java/im/conversations/android/xmpp/model/bind2/BindInlineFeatures.java
similarity index 67%
rename from src/main/java/eu/siacs/conversations/xmpp/bind/Bind2.java
rename to app/src/main/java/im/conversations/android/xmpp/model/bind2/BindInlineFeatures.java
index 21c957a0f..b8e825b03 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/bind/Bind2.java
+++ b/app/src/main/java/im/conversations/android/xmpp/model/bind2/BindInlineFeatures.java
@@ -1,22 +1,19 @@
-package eu.siacs.conversations.xmpp.bind;
+package im.conversations.android.xmpp.model.bind2;
import com.google.common.collect.Collections2;
-
+import im.conversations.android.xml.Element;
+import im.conversations.android.xml.Namespace;
+import im.conversations.android.xmpp.model.sasl2.Inline;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
-import eu.siacs.conversations.xml.Element;
-import eu.siacs.conversations.xml.Namespace;
+public class BindInlineFeatures {
-public class Bind2 {
+ public static final Collection QUICKSTART_FEATURES =
+ Arrays.asList(Namespace.CARBONS, Namespace.STREAM_MANAGEMENT);
- public static final Collection QUICKSTART_FEATURES = Arrays.asList(
- Namespace.CARBONS,
- Namespace.STREAM_MANAGEMENT
- );
-
- public static Collection features(final Element inline) {
+ public static Collection get(final Inline inline) {
final Element inlineBind2 =
inline != null ? inline.findChild("bind", Namespace.BIND2) : null;
final Element inlineBind2Inline =
diff --git a/src/main/java/im/conversations/android/xmpp/model/blocking/Block.java b/app/src/main/java/im/conversations/android/xmpp/model/blocking/Block.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/blocking/Block.java
rename to app/src/main/java/im/conversations/android/xmpp/model/blocking/Block.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/blocking/Blocklist.java b/app/src/main/java/im/conversations/android/xmpp/model/blocking/Blocklist.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/blocking/Blocklist.java
rename to app/src/main/java/im/conversations/android/xmpp/model/blocking/Blocklist.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/blocking/Item.java b/app/src/main/java/im/conversations/android/xmpp/model/blocking/Item.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/blocking/Item.java
rename to app/src/main/java/im/conversations/android/xmpp/model/blocking/Item.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/blocking/Unblock.java b/app/src/main/java/im/conversations/android/xmpp/model/blocking/Unblock.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/blocking/Unblock.java
rename to app/src/main/java/im/conversations/android/xmpp/model/blocking/Unblock.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/blocking/package-info.java b/app/src/main/java/im/conversations/android/xmpp/model/blocking/package-info.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/blocking/package-info.java
rename to app/src/main/java/im/conversations/android/xmpp/model/blocking/package-info.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/bookmark/Conference.java b/app/src/main/java/im/conversations/android/xmpp/model/bookmark/Conference.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/bookmark/Conference.java
rename to app/src/main/java/im/conversations/android/xmpp/model/bookmark/Conference.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/bookmark/package-info.java b/app/src/main/java/im/conversations/android/xmpp/model/bookmark/package-info.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/bookmark/package-info.java
rename to app/src/main/java/im/conversations/android/xmpp/model/bookmark/package-info.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/capabilties/Capabilities.java b/app/src/main/java/im/conversations/android/xmpp/model/capabilties/Capabilities.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/capabilties/Capabilities.java
rename to app/src/main/java/im/conversations/android/xmpp/model/capabilties/Capabilities.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/capabilties/EntityCapabilities.java b/app/src/main/java/im/conversations/android/xmpp/model/capabilties/EntityCapabilities.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/capabilties/EntityCapabilities.java
rename to app/src/main/java/im/conversations/android/xmpp/model/capabilties/EntityCapabilities.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/capabilties/LegacyCapabilities.java b/app/src/main/java/im/conversations/android/xmpp/model/capabilties/LegacyCapabilities.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/capabilties/LegacyCapabilities.java
rename to app/src/main/java/im/conversations/android/xmpp/model/capabilties/LegacyCapabilities.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/carbons/Enable.java b/app/src/main/java/im/conversations/android/xmpp/model/carbons/Enable.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/carbons/Enable.java
rename to app/src/main/java/im/conversations/android/xmpp/model/carbons/Enable.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/carbons/Received.java b/app/src/main/java/im/conversations/android/xmpp/model/carbons/Received.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/carbons/Received.java
rename to app/src/main/java/im/conversations/android/xmpp/model/carbons/Received.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/carbons/Sent.java b/app/src/main/java/im/conversations/android/xmpp/model/carbons/Sent.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/carbons/Sent.java
rename to app/src/main/java/im/conversations/android/xmpp/model/carbons/Sent.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/carbons/package-info.java b/app/src/main/java/im/conversations/android/xmpp/model/carbons/package-info.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/carbons/package-info.java
rename to app/src/main/java/im/conversations/android/xmpp/model/carbons/package-info.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/correction/Replace.java b/app/src/main/java/im/conversations/android/xmpp/model/correction/Replace.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/correction/Replace.java
rename to app/src/main/java/im/conversations/android/xmpp/model/correction/Replace.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/csi/Active.java b/app/src/main/java/im/conversations/android/xmpp/model/csi/Active.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/csi/Active.java
rename to app/src/main/java/im/conversations/android/xmpp/model/csi/Active.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/csi/Inactive.java b/app/src/main/java/im/conversations/android/xmpp/model/csi/Inactive.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/csi/Inactive.java
rename to app/src/main/java/im/conversations/android/xmpp/model/csi/Inactive.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/csi/package-info.java b/app/src/main/java/im/conversations/android/xmpp/model/csi/package-info.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/csi/package-info.java
rename to app/src/main/java/im/conversations/android/xmpp/model/csi/package-info.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/data/Data.java b/app/src/main/java/im/conversations/android/xmpp/model/data/Data.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/data/Data.java
rename to app/src/main/java/im/conversations/android/xmpp/model/data/Data.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/data/Field.java b/app/src/main/java/im/conversations/android/xmpp/model/data/Field.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/data/Field.java
rename to app/src/main/java/im/conversations/android/xmpp/model/data/Field.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/data/Value.java b/app/src/main/java/im/conversations/android/xmpp/model/data/Value.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/data/Value.java
rename to app/src/main/java/im/conversations/android/xmpp/model/data/Value.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/data/package-info.java b/app/src/main/java/im/conversations/android/xmpp/model/data/package-info.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/data/package-info.java
rename to app/src/main/java/im/conversations/android/xmpp/model/data/package-info.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/delay/Delay.java b/app/src/main/java/im/conversations/android/xmpp/model/delay/Delay.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/delay/Delay.java
rename to app/src/main/java/im/conversations/android/xmpp/model/delay/Delay.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/disco/info/Feature.java b/app/src/main/java/im/conversations/android/xmpp/model/disco/info/Feature.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/disco/info/Feature.java
rename to app/src/main/java/im/conversations/android/xmpp/model/disco/info/Feature.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/disco/info/Identity.java b/app/src/main/java/im/conversations/android/xmpp/model/disco/info/Identity.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/disco/info/Identity.java
rename to app/src/main/java/im/conversations/android/xmpp/model/disco/info/Identity.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/disco/info/InfoQuery.java b/app/src/main/java/im/conversations/android/xmpp/model/disco/info/InfoQuery.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/disco/info/InfoQuery.java
rename to app/src/main/java/im/conversations/android/xmpp/model/disco/info/InfoQuery.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/disco/info/package-info.java b/app/src/main/java/im/conversations/android/xmpp/model/disco/info/package-info.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/disco/info/package-info.java
rename to app/src/main/java/im/conversations/android/xmpp/model/disco/info/package-info.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/disco/items/Item.java b/app/src/main/java/im/conversations/android/xmpp/model/disco/items/Item.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/disco/items/Item.java
rename to app/src/main/java/im/conversations/android/xmpp/model/disco/items/Item.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/disco/items/ItemsQuery.java b/app/src/main/java/im/conversations/android/xmpp/model/disco/items/ItemsQuery.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/disco/items/ItemsQuery.java
rename to app/src/main/java/im/conversations/android/xmpp/model/disco/items/ItemsQuery.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/disco/items/package-info.java b/app/src/main/java/im/conversations/android/xmpp/model/disco/items/package-info.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/disco/items/package-info.java
rename to app/src/main/java/im/conversations/android/xmpp/model/disco/items/package-info.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/error/Condition.java b/app/src/main/java/im/conversations/android/xmpp/model/error/Condition.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/error/Condition.java
rename to app/src/main/java/im/conversations/android/xmpp/model/error/Condition.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/error/Error.java b/app/src/main/java/im/conversations/android/xmpp/model/error/Error.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/error/Error.java
rename to app/src/main/java/im/conversations/android/xmpp/model/error/Error.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/error/Text.java b/app/src/main/java/im/conversations/android/xmpp/model/error/Text.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/error/Text.java
rename to app/src/main/java/im/conversations/android/xmpp/model/error/Text.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/forward/Forwarded.java b/app/src/main/java/im/conversations/android/xmpp/model/forward/Forwarded.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/forward/Forwarded.java
rename to app/src/main/java/im/conversations/android/xmpp/model/forward/Forwarded.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/jabber/Body.java b/app/src/main/java/im/conversations/android/xmpp/model/jabber/Body.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/jabber/Body.java
rename to app/src/main/java/im/conversations/android/xmpp/model/jabber/Body.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/jabber/Thread.java b/app/src/main/java/im/conversations/android/xmpp/model/jabber/Thread.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/jabber/Thread.java
rename to app/src/main/java/im/conversations/android/xmpp/model/jabber/Thread.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/jabber/package-info.java b/app/src/main/java/im/conversations/android/xmpp/model/jabber/package-info.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/jabber/package-info.java
rename to app/src/main/java/im/conversations/android/xmpp/model/jabber/package-info.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/mam/Result.java b/app/src/main/java/im/conversations/android/xmpp/model/mam/Result.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/mam/Result.java
rename to app/src/main/java/im/conversations/android/xmpp/model/mam/Result.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/mam/package-info.java b/app/src/main/java/im/conversations/android/xmpp/model/mam/package-info.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/mam/package-info.java
rename to app/src/main/java/im/conversations/android/xmpp/model/mam/package-info.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/markers/Displayed.java b/app/src/main/java/im/conversations/android/xmpp/model/markers/Displayed.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/markers/Displayed.java
rename to app/src/main/java/im/conversations/android/xmpp/model/markers/Displayed.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/markers/Markable.java b/app/src/main/java/im/conversations/android/xmpp/model/markers/Markable.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/markers/Markable.java
rename to app/src/main/java/im/conversations/android/xmpp/model/markers/Markable.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/markers/Received.java b/app/src/main/java/im/conversations/android/xmpp/model/markers/Received.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/markers/Received.java
rename to app/src/main/java/im/conversations/android/xmpp/model/markers/Received.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/markers/package-info.java b/app/src/main/java/im/conversations/android/xmpp/model/markers/package-info.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/markers/package-info.java
rename to app/src/main/java/im/conversations/android/xmpp/model/markers/package-info.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/muc/Affiliation.java b/app/src/main/java/im/conversations/android/xmpp/model/muc/Affiliation.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/muc/Affiliation.java
rename to app/src/main/java/im/conversations/android/xmpp/model/muc/Affiliation.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/muc/Role.java b/app/src/main/java/im/conversations/android/xmpp/model/muc/Role.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/muc/Role.java
rename to app/src/main/java/im/conversations/android/xmpp/model/muc/Role.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/muc/user/MultiUserChat.java b/app/src/main/java/im/conversations/android/xmpp/model/muc/user/MultiUserChat.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/muc/user/MultiUserChat.java
rename to app/src/main/java/im/conversations/android/xmpp/model/muc/user/MultiUserChat.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/muc/user/package-info.java b/app/src/main/java/im/conversations/android/xmpp/model/muc/user/package-info.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/muc/user/package-info.java
rename to app/src/main/java/im/conversations/android/xmpp/model/muc/user/package-info.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/nick/Nick.java b/app/src/main/java/im/conversations/android/xmpp/model/nick/Nick.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/nick/Nick.java
rename to app/src/main/java/im/conversations/android/xmpp/model/nick/Nick.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/occupant/OccupantId.java b/app/src/main/java/im/conversations/android/xmpp/model/occupant/OccupantId.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/occupant/OccupantId.java
rename to app/src/main/java/im/conversations/android/xmpp/model/occupant/OccupantId.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/oob/OutOfBandData.java b/app/src/main/java/im/conversations/android/xmpp/model/oob/OutOfBandData.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/oob/OutOfBandData.java
rename to app/src/main/java/im/conversations/android/xmpp/model/oob/OutOfBandData.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/oob/URL.java b/app/src/main/java/im/conversations/android/xmpp/model/oob/URL.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/oob/URL.java
rename to app/src/main/java/im/conversations/android/xmpp/model/oob/URL.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/oob/package-info.java b/app/src/main/java/im/conversations/android/xmpp/model/oob/package-info.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/oob/package-info.java
rename to app/src/main/java/im/conversations/android/xmpp/model/oob/package-info.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/pars/PreAuth.java b/app/src/main/java/im/conversations/android/xmpp/model/pars/PreAuth.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/pars/PreAuth.java
rename to app/src/main/java/im/conversations/android/xmpp/model/pars/PreAuth.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/ping/Ping.java b/app/src/main/java/im/conversations/android/xmpp/model/ping/Ping.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/ping/Ping.java
rename to app/src/main/java/im/conversations/android/xmpp/model/ping/Ping.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/Item.java b/app/src/main/java/im/conversations/android/xmpp/model/pubsub/Item.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/pubsub/Item.java
rename to app/src/main/java/im/conversations/android/xmpp/model/pubsub/Item.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/Items.java b/app/src/main/java/im/conversations/android/xmpp/model/pubsub/Items.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/pubsub/Items.java
rename to app/src/main/java/im/conversations/android/xmpp/model/pubsub/Items.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/PubSub.java b/app/src/main/java/im/conversations/android/xmpp/model/pubsub/PubSub.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/pubsub/PubSub.java
rename to app/src/main/java/im/conversations/android/xmpp/model/pubsub/PubSub.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/Publish.java b/app/src/main/java/im/conversations/android/xmpp/model/pubsub/Publish.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/pubsub/Publish.java
rename to app/src/main/java/im/conversations/android/xmpp/model/pubsub/Publish.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/PublishOptions.java b/app/src/main/java/im/conversations/android/xmpp/model/pubsub/PublishOptions.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/pubsub/PublishOptions.java
rename to app/src/main/java/im/conversations/android/xmpp/model/pubsub/PublishOptions.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/Retract.java b/app/src/main/java/im/conversations/android/xmpp/model/pubsub/Retract.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/pubsub/Retract.java
rename to app/src/main/java/im/conversations/android/xmpp/model/pubsub/Retract.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/error/PubSubError.java b/app/src/main/java/im/conversations/android/xmpp/model/pubsub/error/PubSubError.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/pubsub/error/PubSubError.java
rename to app/src/main/java/im/conversations/android/xmpp/model/pubsub/error/PubSubError.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/error/package-info.java b/app/src/main/java/im/conversations/android/xmpp/model/pubsub/error/package-info.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/pubsub/error/package-info.java
rename to app/src/main/java/im/conversations/android/xmpp/model/pubsub/error/package-info.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/event/Event.java b/app/src/main/java/im/conversations/android/xmpp/model/pubsub/event/Event.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/pubsub/event/Event.java
rename to app/src/main/java/im/conversations/android/xmpp/model/pubsub/event/Event.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/event/Purge.java b/app/src/main/java/im/conversations/android/xmpp/model/pubsub/event/Purge.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/pubsub/event/Purge.java
rename to app/src/main/java/im/conversations/android/xmpp/model/pubsub/event/Purge.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/event/Retract.java b/app/src/main/java/im/conversations/android/xmpp/model/pubsub/event/Retract.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/pubsub/event/Retract.java
rename to app/src/main/java/im/conversations/android/xmpp/model/pubsub/event/Retract.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/event/package-info.java b/app/src/main/java/im/conversations/android/xmpp/model/pubsub/event/package-info.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/pubsub/event/package-info.java
rename to app/src/main/java/im/conversations/android/xmpp/model/pubsub/event/package-info.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/owner/Configure.java b/app/src/main/java/im/conversations/android/xmpp/model/pubsub/owner/Configure.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/pubsub/owner/Configure.java
rename to app/src/main/java/im/conversations/android/xmpp/model/pubsub/owner/Configure.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/owner/PubSubOwner.java b/app/src/main/java/im/conversations/android/xmpp/model/pubsub/owner/PubSubOwner.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/pubsub/owner/PubSubOwner.java
rename to app/src/main/java/im/conversations/android/xmpp/model/pubsub/owner/PubSubOwner.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/owner/package-info.java b/app/src/main/java/im/conversations/android/xmpp/model/pubsub/owner/package-info.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/pubsub/owner/package-info.java
rename to app/src/main/java/im/conversations/android/xmpp/model/pubsub/owner/package-info.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/pubsub/package-info.java b/app/src/main/java/im/conversations/android/xmpp/model/pubsub/package-info.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/pubsub/package-info.java
rename to app/src/main/java/im/conversations/android/xmpp/model/pubsub/package-info.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/reactions/Reaction.java b/app/src/main/java/im/conversations/android/xmpp/model/reactions/Reaction.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/reactions/Reaction.java
rename to app/src/main/java/im/conversations/android/xmpp/model/reactions/Reaction.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/reactions/Reactions.java b/app/src/main/java/im/conversations/android/xmpp/model/reactions/Reactions.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/reactions/Reactions.java
rename to app/src/main/java/im/conversations/android/xmpp/model/reactions/Reactions.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/reactions/package-info.java b/app/src/main/java/im/conversations/android/xmpp/model/reactions/package-info.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/reactions/package-info.java
rename to app/src/main/java/im/conversations/android/xmpp/model/reactions/package-info.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/receipts/Received.java b/app/src/main/java/im/conversations/android/xmpp/model/receipts/Received.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/receipts/Received.java
rename to app/src/main/java/im/conversations/android/xmpp/model/receipts/Received.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/receipts/Request.java b/app/src/main/java/im/conversations/android/xmpp/model/receipts/Request.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/receipts/Request.java
rename to app/src/main/java/im/conversations/android/xmpp/model/receipts/Request.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/receipts/package-info.java b/app/src/main/java/im/conversations/android/xmpp/model/receipts/package-info.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/receipts/package-info.java
rename to app/src/main/java/im/conversations/android/xmpp/model/receipts/package-info.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/register/Instructions.java b/app/src/main/java/im/conversations/android/xmpp/model/register/Instructions.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/register/Instructions.java
rename to app/src/main/java/im/conversations/android/xmpp/model/register/Instructions.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/register/Password.java b/app/src/main/java/im/conversations/android/xmpp/model/register/Password.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/register/Password.java
rename to app/src/main/java/im/conversations/android/xmpp/model/register/Password.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/register/Register.java b/app/src/main/java/im/conversations/android/xmpp/model/register/Register.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/register/Register.java
rename to app/src/main/java/im/conversations/android/xmpp/model/register/Register.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/register/Remove.java b/app/src/main/java/im/conversations/android/xmpp/model/register/Remove.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/register/Remove.java
rename to app/src/main/java/im/conversations/android/xmpp/model/register/Remove.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/register/Username.java b/app/src/main/java/im/conversations/android/xmpp/model/register/Username.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/register/Username.java
rename to app/src/main/java/im/conversations/android/xmpp/model/register/Username.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/register/package-info.java b/app/src/main/java/im/conversations/android/xmpp/model/register/package-info.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/register/package-info.java
rename to app/src/main/java/im/conversations/android/xmpp/model/register/package-info.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/reply/Reply.java b/app/src/main/java/im/conversations/android/xmpp/model/reply/Reply.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/reply/Reply.java
rename to app/src/main/java/im/conversations/android/xmpp/model/reply/Reply.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/retract/Retract.java b/app/src/main/java/im/conversations/android/xmpp/model/retract/Retract.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/retract/Retract.java
rename to app/src/main/java/im/conversations/android/xmpp/model/retract/Retract.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/retract/Retracted.java b/app/src/main/java/im/conversations/android/xmpp/model/retract/Retracted.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/retract/Retracted.java
rename to app/src/main/java/im/conversations/android/xmpp/model/retract/Retracted.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/retract/package-info.java b/app/src/main/java/im/conversations/android/xmpp/model/retract/package-info.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/retract/package-info.java
rename to app/src/main/java/im/conversations/android/xmpp/model/retract/package-info.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/roster/Group.java b/app/src/main/java/im/conversations/android/xmpp/model/roster/Group.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/roster/Group.java
rename to app/src/main/java/im/conversations/android/xmpp/model/roster/Group.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/roster/Item.java b/app/src/main/java/im/conversations/android/xmpp/model/roster/Item.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/roster/Item.java
rename to app/src/main/java/im/conversations/android/xmpp/model/roster/Item.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/roster/Query.java b/app/src/main/java/im/conversations/android/xmpp/model/roster/Query.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/roster/Query.java
rename to app/src/main/java/im/conversations/android/xmpp/model/roster/Query.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/roster/package-info.java b/app/src/main/java/im/conversations/android/xmpp/model/roster/package-info.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/roster/package-info.java
rename to app/src/main/java/im/conversations/android/xmpp/model/roster/package-info.java
diff --git a/app/src/main/java/im/conversations/android/xmpp/model/sasl2/Inline.java b/app/src/main/java/im/conversations/android/xmpp/model/sasl2/Inline.java
new file mode 100644
index 000000000..e424cb0dd
--- /dev/null
+++ b/app/src/main/java/im/conversations/android/xmpp/model/sasl2/Inline.java
@@ -0,0 +1,12 @@
+package im.conversations.android.xmpp.model.sasl2;
+
+import im.conversations.android.annotation.XmlElement;
+import im.conversations.android.xmpp.model.Extension;
+
+@XmlElement
+public class Inline extends Extension {
+
+ public Inline() {
+ super(Inline.class);
+ }
+}
diff --git a/app/src/main/java/im/conversations/android/xmpp/model/sasl2/package-info.java b/app/src/main/java/im/conversations/android/xmpp/model/sasl2/package-info.java
new file mode 100644
index 000000000..36f8927ff
--- /dev/null
+++ b/app/src/main/java/im/conversations/android/xmpp/model/sasl2/package-info.java
@@ -0,0 +1,5 @@
+@XmlPackage(namespace = Namespace.SASL_2)
+package im.conversations.android.xmpp.model.sasl2;
+
+import im.conversations.android.annotation.XmlPackage;
+import im.conversations.android.xml.Namespace;
diff --git a/src/main/java/im/conversations/android/xmpp/model/sm/Ack.java b/app/src/main/java/im/conversations/android/xmpp/model/sm/Ack.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/sm/Ack.java
rename to app/src/main/java/im/conversations/android/xmpp/model/sm/Ack.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/sm/Enable.java b/app/src/main/java/im/conversations/android/xmpp/model/sm/Enable.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/sm/Enable.java
rename to app/src/main/java/im/conversations/android/xmpp/model/sm/Enable.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/sm/Request.java b/app/src/main/java/im/conversations/android/xmpp/model/sm/Request.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/sm/Request.java
rename to app/src/main/java/im/conversations/android/xmpp/model/sm/Request.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/sm/Resume.java b/app/src/main/java/im/conversations/android/xmpp/model/sm/Resume.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/sm/Resume.java
rename to app/src/main/java/im/conversations/android/xmpp/model/sm/Resume.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/sm/package-info.java b/app/src/main/java/im/conversations/android/xmpp/model/sm/package-info.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/sm/package-info.java
rename to app/src/main/java/im/conversations/android/xmpp/model/sm/package-info.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/stanza/Iq.java b/app/src/main/java/im/conversations/android/xmpp/model/stanza/Iq.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/stanza/Iq.java
rename to app/src/main/java/im/conversations/android/xmpp/model/stanza/Iq.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/stanza/Message.java b/app/src/main/java/im/conversations/android/xmpp/model/stanza/Message.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/stanza/Message.java
rename to app/src/main/java/im/conversations/android/xmpp/model/stanza/Message.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/stanza/Presence.java b/app/src/main/java/im/conversations/android/xmpp/model/stanza/Presence.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/stanza/Presence.java
rename to app/src/main/java/im/conversations/android/xmpp/model/stanza/Presence.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/stanza/Stanza.java b/app/src/main/java/im/conversations/android/xmpp/model/stanza/Stanza.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/stanza/Stanza.java
rename to app/src/main/java/im/conversations/android/xmpp/model/stanza/Stanza.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/stanza/package-info.java b/app/src/main/java/im/conversations/android/xmpp/model/stanza/package-info.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/stanza/package-info.java
rename to app/src/main/java/im/conversations/android/xmpp/model/stanza/package-info.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/state/Active.java b/app/src/main/java/im/conversations/android/xmpp/model/state/Active.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/state/Active.java
rename to app/src/main/java/im/conversations/android/xmpp/model/state/Active.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/state/ChatStateNotification.java b/app/src/main/java/im/conversations/android/xmpp/model/state/ChatStateNotification.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/state/ChatStateNotification.java
rename to app/src/main/java/im/conversations/android/xmpp/model/state/ChatStateNotification.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/state/Composing.java b/app/src/main/java/im/conversations/android/xmpp/model/state/Composing.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/state/Composing.java
rename to app/src/main/java/im/conversations/android/xmpp/model/state/Composing.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/state/Gone.java b/app/src/main/java/im/conversations/android/xmpp/model/state/Gone.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/state/Gone.java
rename to app/src/main/java/im/conversations/android/xmpp/model/state/Gone.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/state/Inactive.java b/app/src/main/java/im/conversations/android/xmpp/model/state/Inactive.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/state/Inactive.java
rename to app/src/main/java/im/conversations/android/xmpp/model/state/Inactive.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/state/Paused.java b/app/src/main/java/im/conversations/android/xmpp/model/state/Paused.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/state/Paused.java
rename to app/src/main/java/im/conversations/android/xmpp/model/state/Paused.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/state/package-info.java b/app/src/main/java/im/conversations/android/xmpp/model/state/package-info.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/state/package-info.java
rename to app/src/main/java/im/conversations/android/xmpp/model/state/package-info.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/streams/Features.java b/app/src/main/java/im/conversations/android/xmpp/model/streams/Features.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/streams/Features.java
rename to app/src/main/java/im/conversations/android/xmpp/model/streams/Features.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/streams/package-info.java b/app/src/main/java/im/conversations/android/xmpp/model/streams/package-info.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/streams/package-info.java
rename to app/src/main/java/im/conversations/android/xmpp/model/streams/package-info.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/unique/OriginId.java b/app/src/main/java/im/conversations/android/xmpp/model/unique/OriginId.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/unique/OriginId.java
rename to app/src/main/java/im/conversations/android/xmpp/model/unique/OriginId.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/unique/StanzaId.java b/app/src/main/java/im/conversations/android/xmpp/model/unique/StanzaId.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/unique/StanzaId.java
rename to app/src/main/java/im/conversations/android/xmpp/model/unique/StanzaId.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/unique/package-info.java b/app/src/main/java/im/conversations/android/xmpp/model/unique/package-info.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/unique/package-info.java
rename to app/src/main/java/im/conversations/android/xmpp/model/unique/package-info.java
diff --git a/src/main/java/im/conversations/android/xmpp/model/version/Version.java b/app/src/main/java/im/conversations/android/xmpp/model/version/Version.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/model/version/Version.java
rename to app/src/main/java/im/conversations/android/xmpp/model/version/Version.java
diff --git a/src/main/java/im/conversations/android/xmpp/processor/BindProcessor.java b/app/src/main/java/im/conversations/android/xmpp/processor/BindProcessor.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/processor/BindProcessor.java
rename to app/src/main/java/im/conversations/android/xmpp/processor/BindProcessor.java
diff --git a/src/main/java/im/conversations/android/xmpp/processor/IqProcessor.java b/app/src/main/java/im/conversations/android/xmpp/processor/IqProcessor.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/processor/IqProcessor.java
rename to app/src/main/java/im/conversations/android/xmpp/processor/IqProcessor.java
diff --git a/src/main/java/im/conversations/android/xmpp/processor/MessageAcknowledgeProcessor.java b/app/src/main/java/im/conversations/android/xmpp/processor/MessageAcknowledgeProcessor.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/processor/MessageAcknowledgeProcessor.java
rename to app/src/main/java/im/conversations/android/xmpp/processor/MessageAcknowledgeProcessor.java
diff --git a/src/main/java/im/conversations/android/xmpp/processor/MessageProcessor.java b/app/src/main/java/im/conversations/android/xmpp/processor/MessageProcessor.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/processor/MessageProcessor.java
rename to app/src/main/java/im/conversations/android/xmpp/processor/MessageProcessor.java
diff --git a/src/main/java/im/conversations/android/xmpp/processor/PresenceProcessor.java b/app/src/main/java/im/conversations/android/xmpp/processor/PresenceProcessor.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/processor/PresenceProcessor.java
rename to app/src/main/java/im/conversations/android/xmpp/processor/PresenceProcessor.java
diff --git a/src/main/java/im/conversations/android/xmpp/sasl/Anonymous.java b/app/src/main/java/im/conversations/android/xmpp/sasl/Anonymous.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/sasl/Anonymous.java
rename to app/src/main/java/im/conversations/android/xmpp/sasl/Anonymous.java
diff --git a/src/main/java/im/conversations/android/xmpp/sasl/ChannelBinding.java b/app/src/main/java/im/conversations/android/xmpp/sasl/ChannelBinding.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/sasl/ChannelBinding.java
rename to app/src/main/java/im/conversations/android/xmpp/sasl/ChannelBinding.java
diff --git a/src/main/java/im/conversations/android/xmpp/sasl/ChannelBindingMechanism.java b/app/src/main/java/im/conversations/android/xmpp/sasl/ChannelBindingMechanism.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/sasl/ChannelBindingMechanism.java
rename to app/src/main/java/im/conversations/android/xmpp/sasl/ChannelBindingMechanism.java
diff --git a/src/main/java/im/conversations/android/xmpp/sasl/DigestMd5.java b/app/src/main/java/im/conversations/android/xmpp/sasl/DigestMd5.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/sasl/DigestMd5.java
rename to app/src/main/java/im/conversations/android/xmpp/sasl/DigestMd5.java
diff --git a/src/main/java/im/conversations/android/xmpp/sasl/External.java b/app/src/main/java/im/conversations/android/xmpp/sasl/External.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/sasl/External.java
rename to app/src/main/java/im/conversations/android/xmpp/sasl/External.java
diff --git a/src/main/java/im/conversations/android/xmpp/sasl/HashedToken.java b/app/src/main/java/im/conversations/android/xmpp/sasl/HashedToken.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/sasl/HashedToken.java
rename to app/src/main/java/im/conversations/android/xmpp/sasl/HashedToken.java
diff --git a/src/main/java/im/conversations/android/xmpp/sasl/HashedTokenSha256.java b/app/src/main/java/im/conversations/android/xmpp/sasl/HashedTokenSha256.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/sasl/HashedTokenSha256.java
rename to app/src/main/java/im/conversations/android/xmpp/sasl/HashedTokenSha256.java
diff --git a/src/main/java/im/conversations/android/xmpp/sasl/HashedTokenSha512.java b/app/src/main/java/im/conversations/android/xmpp/sasl/HashedTokenSha512.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/sasl/HashedTokenSha512.java
rename to app/src/main/java/im/conversations/android/xmpp/sasl/HashedTokenSha512.java
diff --git a/src/main/java/im/conversations/android/xmpp/sasl/Plain.java b/app/src/main/java/im/conversations/android/xmpp/sasl/Plain.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/sasl/Plain.java
rename to app/src/main/java/im/conversations/android/xmpp/sasl/Plain.java
diff --git a/src/main/java/im/conversations/android/xmpp/sasl/SaslMechanism.java b/app/src/main/java/im/conversations/android/xmpp/sasl/SaslMechanism.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/sasl/SaslMechanism.java
rename to app/src/main/java/im/conversations/android/xmpp/sasl/SaslMechanism.java
diff --git a/src/main/java/im/conversations/android/xmpp/sasl/ScramMechanism.java b/app/src/main/java/im/conversations/android/xmpp/sasl/ScramMechanism.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/sasl/ScramMechanism.java
rename to app/src/main/java/im/conversations/android/xmpp/sasl/ScramMechanism.java
diff --git a/src/main/java/im/conversations/android/xmpp/sasl/ScramPlusMechanism.java b/app/src/main/java/im/conversations/android/xmpp/sasl/ScramPlusMechanism.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/sasl/ScramPlusMechanism.java
rename to app/src/main/java/im/conversations/android/xmpp/sasl/ScramPlusMechanism.java
diff --git a/src/main/java/im/conversations/android/xmpp/sasl/ScramSha1.java b/app/src/main/java/im/conversations/android/xmpp/sasl/ScramSha1.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/sasl/ScramSha1.java
rename to app/src/main/java/im/conversations/android/xmpp/sasl/ScramSha1.java
diff --git a/src/main/java/im/conversations/android/xmpp/sasl/ScramSha1Plus.java b/app/src/main/java/im/conversations/android/xmpp/sasl/ScramSha1Plus.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/sasl/ScramSha1Plus.java
rename to app/src/main/java/im/conversations/android/xmpp/sasl/ScramSha1Plus.java
diff --git a/src/main/java/im/conversations/android/xmpp/sasl/ScramSha256.java b/app/src/main/java/im/conversations/android/xmpp/sasl/ScramSha256.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/sasl/ScramSha256.java
rename to app/src/main/java/im/conversations/android/xmpp/sasl/ScramSha256.java
diff --git a/src/main/java/im/conversations/android/xmpp/sasl/ScramSha256Plus.java b/app/src/main/java/im/conversations/android/xmpp/sasl/ScramSha256Plus.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/sasl/ScramSha256Plus.java
rename to app/src/main/java/im/conversations/android/xmpp/sasl/ScramSha256Plus.java
diff --git a/src/main/java/im/conversations/android/xmpp/sasl/ScramSha512.java b/app/src/main/java/im/conversations/android/xmpp/sasl/ScramSha512.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/sasl/ScramSha512.java
rename to app/src/main/java/im/conversations/android/xmpp/sasl/ScramSha512.java
diff --git a/src/main/java/im/conversations/android/xmpp/sasl/ScramSha512Plus.java b/app/src/main/java/im/conversations/android/xmpp/sasl/ScramSha512Plus.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/sasl/ScramSha512Plus.java
rename to app/src/main/java/im/conversations/android/xmpp/sasl/ScramSha512Plus.java
diff --git a/src/main/java/im/conversations/android/xmpp/sasl/Tokenizer.java b/app/src/main/java/im/conversations/android/xmpp/sasl/Tokenizer.java
similarity index 100%
rename from src/main/java/im/conversations/android/xmpp/sasl/Tokenizer.java
rename to app/src/main/java/im/conversations/android/xmpp/sasl/Tokenizer.java
diff --git a/src/main/res/anim/slide_from_left.xml b/app/src/main/res/anim/slide_from_left.xml
similarity index 100%
rename from src/main/res/anim/slide_from_left.xml
rename to app/src/main/res/anim/slide_from_left.xml
diff --git a/src/main/res/anim/slide_from_right.xml b/app/src/main/res/anim/slide_from_right.xml
similarity index 100%
rename from src/main/res/anim/slide_from_right.xml
rename to app/src/main/res/anim/slide_from_right.xml
diff --git a/src/main/res/anim/slide_to_left.xml b/app/src/main/res/anim/slide_to_left.xml
similarity index 100%
rename from src/main/res/anim/slide_to_left.xml
rename to app/src/main/res/anim/slide_to_left.xml
diff --git a/src/main/res/anim/slide_to_right.xml b/app/src/main/res/anim/slide_to_right.xml
similarity index 100%
rename from src/main/res/anim/slide_to_right.xml
rename to app/src/main/res/anim/slide_to_right.xml
diff --git a/src/main/res/drawable/ic_account_circle_24dp.xml b/app/src/main/res/drawable/ic_account_circle_24dp.xml
similarity index 100%
rename from src/main/res/drawable/ic_account_circle_24dp.xml
rename to app/src/main/res/drawable/ic_account_circle_24dp.xml
diff --git a/src/main/res/drawable/ic_settings_24dp.xml b/app/src/main/res/drawable/ic_settings_24dp.xml
similarity index 100%
rename from src/main/res/drawable/ic_settings_24dp.xml
rename to app/src/main/res/drawable/ic_settings_24dp.xml
diff --git a/src/main/res/layout/activity_setup.xml b/app/src/main/res/layout/activity_setup.xml
similarity index 100%
rename from src/main/res/layout/activity_setup.xml
rename to app/src/main/res/layout/activity_setup.xml
diff --git a/src/main/res/layout/fragment_password.xml b/app/src/main/res/layout/fragment_password.xml
similarity index 99%
rename from src/main/res/layout/fragment_password.xml
rename to app/src/main/res/layout/fragment_password.xml
index fa9d2b8ca..46deb9163 100644
--- a/src/main/res/layout/fragment_password.xml
+++ b/app/src/main/res/layout/fragment_password.xml
@@ -33,7 +33,7 @@
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:title="@string/app_name"
- app:menu="@menu/setup"
+ app:menu="@menu/activity_setup"
app:titleTextColor="?colorPrimary"
app:titleCentered="true" />
diff --git a/src/main/res/layout/fragment_sign_in.xml b/app/src/main/res/layout/fragment_sign_in.xml
similarity index 98%
rename from src/main/res/layout/fragment_sign_in.xml
rename to app/src/main/res/layout/fragment_sign_in.xml
index 018557a7d..0339896e7 100644
--- a/src/main/res/layout/fragment_sign_in.xml
+++ b/app/src/main/res/layout/fragment_sign_in.xml
@@ -32,7 +32,7 @@
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:title="@string/app_name"
- app:menu="@menu/setup"
+ app:menu="@menu/activity_setup"
app:titleTextColor="?colorPrimary"
app:titleCentered="true" />
@@ -111,7 +111,6 @@
android:layout_height="wrap_content"
android:text="@string/no_account_register"
android:layout_centerHorizontal="true"
- android:textColor="?colorSecondary"
android:enabled="@{!setupViewModel.isLoading()}"
android:layout_below="@+id/xmpp_address_input_layout"
/>
diff --git a/src/main/res/menu/setup.xml b/app/src/main/res/menu/activity_setup.xml
similarity index 100%
rename from src/main/res/menu/setup.xml
rename to app/src/main/res/menu/activity_setup.xml
diff --git a/src/main/res/navigation/setup_navigation.xml b/app/src/main/res/navigation/setup_navigation.xml
similarity index 100%
rename from src/main/res/navigation/setup_navigation.xml
rename to app/src/main/res/navigation/setup_navigation.xml
diff --git a/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml
similarity index 100%
rename from src/main/res/values-ar/strings.xml
rename to app/src/main/res/values-ar/strings.xml
diff --git a/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml
similarity index 100%
rename from src/main/res/values-bg/strings.xml
rename to app/src/main/res/values-bg/strings.xml
diff --git a/src/main/res/values-bn-rIN/strings.xml b/app/src/main/res/values-bn-rIN/strings.xml
similarity index 100%
rename from src/main/res/values-bn-rIN/strings.xml
rename to app/src/main/res/values-bn-rIN/strings.xml
diff --git a/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml
similarity index 100%
rename from src/main/res/values-ca/strings.xml
rename to app/src/main/res/values-ca/strings.xml
diff --git a/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml
similarity index 100%
rename from src/main/res/values-cs/strings.xml
rename to app/src/main/res/values-cs/strings.xml
diff --git a/src/main/res/values-da-rDK/strings.xml b/app/src/main/res/values-da-rDK/strings.xml
similarity index 100%
rename from src/main/res/values-da-rDK/strings.xml
rename to app/src/main/res/values-da-rDK/strings.xml
diff --git a/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
similarity index 100%
rename from src/main/res/values-de/strings.xml
rename to app/src/main/res/values-de/strings.xml
diff --git a/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml
similarity index 100%
rename from src/main/res/values-el/strings.xml
rename to app/src/main/res/values-el/strings.xml
diff --git a/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
similarity index 100%
rename from src/main/res/values-es/strings.xml
rename to app/src/main/res/values-es/strings.xml
diff --git a/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml
similarity index 100%
rename from src/main/res/values-eu/strings.xml
rename to app/src/main/res/values-eu/strings.xml
diff --git a/src/main/res/values-fa-rIR/strings.xml b/app/src/main/res/values-fa-rIR/strings.xml
similarity index 100%
rename from src/main/res/values-fa-rIR/strings.xml
rename to app/src/main/res/values-fa-rIR/strings.xml
diff --git a/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml
similarity index 100%
rename from src/main/res/values-fi/strings.xml
rename to app/src/main/res/values-fi/strings.xml
diff --git a/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
similarity index 100%
rename from src/main/res/values-fr/strings.xml
rename to app/src/main/res/values-fr/strings.xml
diff --git a/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml
similarity index 100%
rename from src/main/res/values-gl/strings.xml
rename to app/src/main/res/values-gl/strings.xml
diff --git a/src/main/res/values-hi-rIN/strings.xml b/app/src/main/res/values-hi-rIN/strings.xml
similarity index 100%
rename from src/main/res/values-hi-rIN/strings.xml
rename to app/src/main/res/values-hi-rIN/strings.xml
diff --git a/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml
similarity index 100%
rename from src/main/res/values-hr/strings.xml
rename to app/src/main/res/values-hr/strings.xml
diff --git a/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml
similarity index 100%
rename from src/main/res/values-hu/strings.xml
rename to app/src/main/res/values-hu/strings.xml
diff --git a/src/main/res/values-id/strings.xml b/app/src/main/res/values-id/strings.xml
similarity index 100%
rename from src/main/res/values-id/strings.xml
rename to app/src/main/res/values-id/strings.xml
diff --git a/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
similarity index 100%
rename from src/main/res/values-it/strings.xml
rename to app/src/main/res/values-it/strings.xml
diff --git a/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml
similarity index 100%
rename from src/main/res/values-iw/strings.xml
rename to app/src/main/res/values-iw/strings.xml
diff --git a/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml
similarity index 100%
rename from src/main/res/values-ja/strings.xml
rename to app/src/main/res/values-ja/strings.xml
diff --git a/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml
similarity index 100%
rename from src/main/res/values-ko/strings.xml
rename to app/src/main/res/values-ko/strings.xml
diff --git a/src/main/res/values-land/bools.xml b/app/src/main/res/values-land/bools.xml
similarity index 100%
rename from src/main/res/values-land/bools.xml
rename to app/src/main/res/values-land/bools.xml
diff --git a/app/src/main/res/values-land/dimens.xml b/app/src/main/res/values-land/dimens.xml
new file mode 100644
index 000000000..66ed64d3d
--- /dev/null
+++ b/app/src/main/res/values-land/dimens.xml
@@ -0,0 +1,5 @@
+
+ 8dp
+ 12dp
+ 272dp
+
diff --git a/src/main/res/values-ml/strings.xml b/app/src/main/res/values-ml/strings.xml
similarity index 100%
rename from src/main/res/values-ml/strings.xml
rename to app/src/main/res/values-ml/strings.xml
diff --git a/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml
similarity index 100%
rename from src/main/res/values-nb-rNO/strings.xml
rename to app/src/main/res/values-nb-rNO/strings.xml
diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml
new file mode 100644
index 000000000..2bbe27628
--- /dev/null
+++ b/app/src/main/res/values-night/themes.xml
@@ -0,0 +1,31 @@
+
+
+
+
diff --git a/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml
similarity index 100%
rename from src/main/res/values-nl/strings.xml
rename to app/src/main/res/values-nl/strings.xml
diff --git a/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
similarity index 100%
rename from src/main/res/values-pl/strings.xml
rename to app/src/main/res/values-pl/strings.xml
diff --git a/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml
similarity index 100%
rename from src/main/res/values-pt-rBR/strings.xml
rename to app/src/main/res/values-pt-rBR/strings.xml
diff --git a/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml
similarity index 100%
rename from src/main/res/values-pt/strings.xml
rename to app/src/main/res/values-pt/strings.xml
diff --git a/src/main/res/values-ro-rRO/strings.xml b/app/src/main/res/values-ro-rRO/strings.xml
similarity index 100%
rename from src/main/res/values-ro-rRO/strings.xml
rename to app/src/main/res/values-ro-rRO/strings.xml
diff --git a/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
similarity index 100%
rename from src/main/res/values-ru/strings.xml
rename to app/src/main/res/values-ru/strings.xml
diff --git a/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml
similarity index 100%
rename from src/main/res/values-sk/strings.xml
rename to app/src/main/res/values-sk/strings.xml
diff --git a/src/main/res/values-sq-rAL/strings.xml b/app/src/main/res/values-sq-rAL/strings.xml
similarity index 100%
rename from src/main/res/values-sq-rAL/strings.xml
rename to app/src/main/res/values-sq-rAL/strings.xml
diff --git a/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml
similarity index 100%
rename from src/main/res/values-sr/strings.xml
rename to app/src/main/res/values-sr/strings.xml
diff --git a/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml
similarity index 100%
rename from src/main/res/values-sv/strings.xml
rename to app/src/main/res/values-sv/strings.xml
diff --git a/src/quicksy/res/values/defaults.xml b/app/src/main/res/values-sw600dp/device.xml
similarity index 52%
rename from src/quicksy/res/values/defaults.xml
rename to app/src/main/res/values-sw600dp/device.xml
index 8f3617396..76b119505 100644
--- a/src/quicksy/res/values/defaults.xml
+++ b/app/src/main/res/values-sw600dp/device.xml
@@ -1,4 +1,4 @@
- always
+ true
diff --git a/src/main/res/values-szl/strings.xml b/app/src/main/res/values-szl/strings.xml
similarity index 100%
rename from src/main/res/values-szl/strings.xml
rename to app/src/main/res/values-szl/strings.xml
diff --git a/src/main/res/values-tr-rTR/strings.xml b/app/src/main/res/values-tr-rTR/strings.xml
similarity index 100%
rename from src/main/res/values-tr-rTR/strings.xml
rename to app/src/main/res/values-tr-rTR/strings.xml
diff --git a/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
similarity index 100%
rename from src/main/res/values-uk/strings.xml
rename to app/src/main/res/values-uk/strings.xml
diff --git a/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml
similarity index 100%
rename from src/main/res/values-vi/strings.xml
rename to app/src/main/res/values-vi/strings.xml
diff --git a/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
similarity index 100%
rename from src/main/res/values-zh-rCN/strings.xml
rename to app/src/main/res/values-zh-rCN/strings.xml
diff --git a/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml
similarity index 100%
rename from src/main/res/values-zh-rTW/strings.xml
rename to app/src/main/res/values-zh-rTW/strings.xml
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
new file mode 100644
index 000000000..fe65a2a6a
--- /dev/null
+++ b/app/src/main/res/values/colors.xml
@@ -0,0 +1,64 @@
+
+
+ #43A047
+ #006E1C
+ #FFFFFF
+ #98F994
+ #002204
+ #52634F
+ #FFFFFF
+ #D5E8CF
+ #111F0F
+ #38656A
+ #FFFFFF
+ #BCEBF0
+ #002023
+ #BA1A1A
+ #FFDAD6
+ #FFFFFF
+ #410002
+ #FCFDF6
+ #1A1C19
+ #FCFDF6
+ #1A1C19
+ #DEE5D8
+ #424940
+ #72796F
+ #F0F1EB
+ #2F312D
+ #7DDC7A
+ #000000
+ #006E1C
+ #C2C9BD
+ #000000
+ #7DDC7A
+ #00390A
+ #005313
+ #98F994
+ #BACCB3
+ #253423
+ #3B4B38
+ #D5E8CF
+ #A0CFD4
+ #00363B
+ #1F4D52
+ #BCEBF0
+ #FFB4AB
+ #93000A
+ #690005
+ #FFDAD6
+ #1A1C19
+ #E2E3DD
+ #1A1C19
+ #E2E3DD
+ #424940
+ #C2C9BD
+ #8C9388
+ #1A1C19
+ #E2E3DD
+ #006E1C
+ #000000
+ #7DDC7A
+ #424940
+ #000000
+
diff --git a/app/src/main/res/values/device.xml b/app/src/main/res/values/device.xml
new file mode 100644
index 000000000..f99fe3e59
--- /dev/null
+++ b/app/src/main/res/values/device.xml
@@ -0,0 +1,4 @@
+
+
+ false
+
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
new file mode 100644
index 000000000..7f85bd709
--- /dev/null
+++ b/app/src/main/res/values/dimens.xml
@@ -0,0 +1,5 @@
+
+ 48dp
+ 336dp
+ 24dp
+
diff --git a/src/main/res/values/integers.xml b/app/src/main/res/values/integers.xml
similarity index 100%
rename from src/main/res/values/integers.xml
rename to app/src/main/res/values/integers.xml
diff --git a/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
similarity index 100%
rename from src/main/res/values/strings.xml
rename to app/src/main/res/values/strings.xml
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
new file mode 100644
index 000000000..ddf74586f
--- /dev/null
+++ b/app/src/main/res/values/themes.xml
@@ -0,0 +1,31 @@
+
+
+
+
diff --git a/app/src/main/res/xml/backup_rules.xml b/app/src/main/res/xml/backup_rules.xml
new file mode 100644
index 000000000..fa0f996d2
--- /dev/null
+++ b/app/src/main/res/xml/backup_rules.xml
@@ -0,0 +1,13 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/xml/data_extraction_rules.xml b/app/src/main/res/xml/data_extraction_rules.xml
new file mode 100644
index 000000000..9ee9997b0
--- /dev/null
+++ b/app/src/main/res/xml/data_extraction_rules.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/quicksy/res/drawable/ic_launcher_foreground.xml b/app/src/quicksy/res/drawable/ic_launcher_foreground.xml
similarity index 100%
rename from src/quicksy/res/drawable/ic_launcher_foreground.xml
rename to app/src/quicksy/res/drawable/ic_launcher_foreground.xml
diff --git a/src/quicksy/res/mipmap-anydpi-v26/new_launcher.xml b/app/src/quicksy/res/mipmap-anydpi-v26/new_launcher.xml
similarity index 100%
rename from src/quicksy/res/mipmap-anydpi-v26/new_launcher.xml
rename to app/src/quicksy/res/mipmap-anydpi-v26/new_launcher.xml
diff --git a/src/quicksy/res/mipmap-anydpi-v26/new_launcher_round.xml b/app/src/quicksy/res/mipmap-anydpi-v26/new_launcher_round.xml
similarity index 100%
rename from src/quicksy/res/mipmap-anydpi-v26/new_launcher_round.xml
rename to app/src/quicksy/res/mipmap-anydpi-v26/new_launcher_round.xml
diff --git a/app/src/quicksy/res/mipmap-hdpi/ic_launcher_background.png b/app/src/quicksy/res/mipmap-hdpi/ic_launcher_background.png
new file mode 100644
index 000000000..0e5a16390
Binary files /dev/null and b/app/src/quicksy/res/mipmap-hdpi/ic_launcher_background.png differ
diff --git a/src/quicksy/res/mipmap-hdpi/new_launcher.png b/app/src/quicksy/res/mipmap-hdpi/new_launcher.png
similarity index 100%
rename from src/quicksy/res/mipmap-hdpi/new_launcher.png
rename to app/src/quicksy/res/mipmap-hdpi/new_launcher.png
diff --git a/src/quicksy/res/mipmap-hdpi/new_launcher_round.png b/app/src/quicksy/res/mipmap-hdpi/new_launcher_round.png
similarity index 100%
rename from src/quicksy/res/mipmap-hdpi/new_launcher_round.png
rename to app/src/quicksy/res/mipmap-hdpi/new_launcher_round.png
diff --git a/app/src/quicksy/res/mipmap-mdpi/ic_launcher_background.png b/app/src/quicksy/res/mipmap-mdpi/ic_launcher_background.png
new file mode 100644
index 000000000..57edd5ca3
Binary files /dev/null and b/app/src/quicksy/res/mipmap-mdpi/ic_launcher_background.png differ
diff --git a/src/quicksy/res/mipmap-mdpi/new_launcher.png b/app/src/quicksy/res/mipmap-mdpi/new_launcher.png
similarity index 100%
rename from src/quicksy/res/mipmap-mdpi/new_launcher.png
rename to app/src/quicksy/res/mipmap-mdpi/new_launcher.png
diff --git a/src/quicksy/res/mipmap-mdpi/new_launcher_round.png b/app/src/quicksy/res/mipmap-mdpi/new_launcher_round.png
similarity index 100%
rename from src/quicksy/res/mipmap-mdpi/new_launcher_round.png
rename to app/src/quicksy/res/mipmap-mdpi/new_launcher_round.png
diff --git a/app/src/quicksy/res/mipmap-xhdpi/ic_launcher_background.png b/app/src/quicksy/res/mipmap-xhdpi/ic_launcher_background.png
new file mode 100644
index 000000000..7e4e772d0
Binary files /dev/null and b/app/src/quicksy/res/mipmap-xhdpi/ic_launcher_background.png differ
diff --git a/src/quicksy/res/mipmap-xhdpi/new_launcher.png b/app/src/quicksy/res/mipmap-xhdpi/new_launcher.png
similarity index 100%
rename from src/quicksy/res/mipmap-xhdpi/new_launcher.png
rename to app/src/quicksy/res/mipmap-xhdpi/new_launcher.png
diff --git a/src/quicksy/res/mipmap-xhdpi/new_launcher_round.png b/app/src/quicksy/res/mipmap-xhdpi/new_launcher_round.png
similarity index 100%
rename from src/quicksy/res/mipmap-xhdpi/new_launcher_round.png
rename to app/src/quicksy/res/mipmap-xhdpi/new_launcher_round.png
diff --git a/app/src/quicksy/res/mipmap-xxhdpi/ic_launcher_background.png b/app/src/quicksy/res/mipmap-xxhdpi/ic_launcher_background.png
new file mode 100644
index 000000000..3ceecd75a
Binary files /dev/null and b/app/src/quicksy/res/mipmap-xxhdpi/ic_launcher_background.png differ
diff --git a/src/quicksy/res/mipmap-xxhdpi/new_launcher.png b/app/src/quicksy/res/mipmap-xxhdpi/new_launcher.png
similarity index 100%
rename from src/quicksy/res/mipmap-xxhdpi/new_launcher.png
rename to app/src/quicksy/res/mipmap-xxhdpi/new_launcher.png
diff --git a/src/quicksy/res/mipmap-xxhdpi/new_launcher_round.png b/app/src/quicksy/res/mipmap-xxhdpi/new_launcher_round.png
similarity index 100%
rename from src/quicksy/res/mipmap-xxhdpi/new_launcher_round.png
rename to app/src/quicksy/res/mipmap-xxhdpi/new_launcher_round.png
diff --git a/app/src/quicksy/res/mipmap-xxxhdpi/ic_launcher_background.png b/app/src/quicksy/res/mipmap-xxxhdpi/ic_launcher_background.png
new file mode 100644
index 000000000..b75f8bc45
Binary files /dev/null and b/app/src/quicksy/res/mipmap-xxxhdpi/ic_launcher_background.png differ
diff --git a/src/quicksy/res/mipmap-xxxhdpi/new_launcher.png b/app/src/quicksy/res/mipmap-xxxhdpi/new_launcher.png
similarity index 100%
rename from src/quicksy/res/mipmap-xxxhdpi/new_launcher.png
rename to app/src/quicksy/res/mipmap-xxxhdpi/new_launcher.png
diff --git a/src/quicksy/res/mipmap-xxxhdpi/new_launcher_round.png b/app/src/quicksy/res/mipmap-xxxhdpi/new_launcher_round.png
similarity index 100%
rename from src/quicksy/res/mipmap-xxxhdpi/new_launcher_round.png
rename to app/src/quicksy/res/mipmap-xxxhdpi/new_launcher_round.png
diff --git a/src/quicksy/res/values-ar/strings.xml b/app/src/quicksy/res/values-ar/strings.xml
similarity index 100%
rename from src/quicksy/res/values-ar/strings.xml
rename to app/src/quicksy/res/values-ar/strings.xml
diff --git a/src/quicksy/res/values-bg/strings.xml b/app/src/quicksy/res/values-bg/strings.xml
similarity index 100%
rename from src/quicksy/res/values-bg/strings.xml
rename to app/src/quicksy/res/values-bg/strings.xml
diff --git a/src/quicksy/res/values-ca/strings.xml b/app/src/quicksy/res/values-ca/strings.xml
similarity index 100%
rename from src/quicksy/res/values-ca/strings.xml
rename to app/src/quicksy/res/values-ca/strings.xml
diff --git a/src/quicksy/res/values-da-rDK/strings.xml b/app/src/quicksy/res/values-da-rDK/strings.xml
similarity index 100%
rename from src/quicksy/res/values-da-rDK/strings.xml
rename to app/src/quicksy/res/values-da-rDK/strings.xml
diff --git a/src/quicksy/res/values-de/strings.xml b/app/src/quicksy/res/values-de/strings.xml
similarity index 100%
rename from src/quicksy/res/values-de/strings.xml
rename to app/src/quicksy/res/values-de/strings.xml
diff --git a/src/quicksy/res/values-el/strings.xml b/app/src/quicksy/res/values-el/strings.xml
similarity index 100%
rename from src/quicksy/res/values-el/strings.xml
rename to app/src/quicksy/res/values-el/strings.xml
diff --git a/src/quicksy/res/values-es/strings.xml b/app/src/quicksy/res/values-es/strings.xml
similarity index 100%
rename from src/quicksy/res/values-es/strings.xml
rename to app/src/quicksy/res/values-es/strings.xml
diff --git a/src/quicksy/res/values-fi/strings.xml b/app/src/quicksy/res/values-fi/strings.xml
similarity index 100%
rename from src/quicksy/res/values-fi/strings.xml
rename to app/src/quicksy/res/values-fi/strings.xml
diff --git a/src/quicksy/res/values-fr/strings.xml b/app/src/quicksy/res/values-fr/strings.xml
similarity index 100%
rename from src/quicksy/res/values-fr/strings.xml
rename to app/src/quicksy/res/values-fr/strings.xml
diff --git a/src/quicksy/res/values-gl/strings.xml b/app/src/quicksy/res/values-gl/strings.xml
similarity index 100%
rename from src/quicksy/res/values-gl/strings.xml
rename to app/src/quicksy/res/values-gl/strings.xml
diff --git a/src/quicksy/res/values-hr/strings.xml b/app/src/quicksy/res/values-hr/strings.xml
similarity index 100%
rename from src/quicksy/res/values-hr/strings.xml
rename to app/src/quicksy/res/values-hr/strings.xml
diff --git a/src/quicksy/res/values-hu/strings.xml b/app/src/quicksy/res/values-hu/strings.xml
similarity index 100%
rename from src/quicksy/res/values-hu/strings.xml
rename to app/src/quicksy/res/values-hu/strings.xml
diff --git a/src/quicksy/res/values-id/strings.xml b/app/src/quicksy/res/values-id/strings.xml
similarity index 100%
rename from src/quicksy/res/values-id/strings.xml
rename to app/src/quicksy/res/values-id/strings.xml
diff --git a/src/quicksy/res/values-it/strings.xml b/app/src/quicksy/res/values-it/strings.xml
similarity index 100%
rename from src/quicksy/res/values-it/strings.xml
rename to app/src/quicksy/res/values-it/strings.xml
diff --git a/src/quicksy/res/values-ja/strings.xml b/app/src/quicksy/res/values-ja/strings.xml
similarity index 100%
rename from src/quicksy/res/values-ja/strings.xml
rename to app/src/quicksy/res/values-ja/strings.xml
diff --git a/src/quicksy/res/values-nl/strings.xml b/app/src/quicksy/res/values-nl/strings.xml
similarity index 100%
rename from src/quicksy/res/values-nl/strings.xml
rename to app/src/quicksy/res/values-nl/strings.xml
diff --git a/src/quicksy/res/values-pl/strings.xml b/app/src/quicksy/res/values-pl/strings.xml
similarity index 100%
rename from src/quicksy/res/values-pl/strings.xml
rename to app/src/quicksy/res/values-pl/strings.xml
diff --git a/src/quicksy/res/values-pt-rBR/strings.xml b/app/src/quicksy/res/values-pt-rBR/strings.xml
similarity index 100%
rename from src/quicksy/res/values-pt-rBR/strings.xml
rename to app/src/quicksy/res/values-pt-rBR/strings.xml
diff --git a/src/quicksy/res/values-ro-rRO/strings.xml b/app/src/quicksy/res/values-ro-rRO/strings.xml
similarity index 100%
rename from src/quicksy/res/values-ro-rRO/strings.xml
rename to app/src/quicksy/res/values-ro-rRO/strings.xml
diff --git a/src/quicksy/res/values-ru/strings.xml b/app/src/quicksy/res/values-ru/strings.xml
similarity index 100%
rename from src/quicksy/res/values-ru/strings.xml
rename to app/src/quicksy/res/values-ru/strings.xml
diff --git a/src/quicksy/res/values-sk/strings.xml b/app/src/quicksy/res/values-sk/strings.xml
similarity index 100%
rename from src/quicksy/res/values-sk/strings.xml
rename to app/src/quicksy/res/values-sk/strings.xml
diff --git a/src/quicksy/res/values-sq/strings.xml b/app/src/quicksy/res/values-sq/strings.xml
similarity index 100%
rename from src/quicksy/res/values-sq/strings.xml
rename to app/src/quicksy/res/values-sq/strings.xml
diff --git a/src/quicksy/res/values-sv/strings.xml b/app/src/quicksy/res/values-sv/strings.xml
similarity index 100%
rename from src/quicksy/res/values-sv/strings.xml
rename to app/src/quicksy/res/values-sv/strings.xml
diff --git a/src/quicksy/res/values-szl/strings.xml b/app/src/quicksy/res/values-szl/strings.xml
similarity index 100%
rename from src/quicksy/res/values-szl/strings.xml
rename to app/src/quicksy/res/values-szl/strings.xml
diff --git a/src/quicksy/res/values-tr-rTR/strings.xml b/app/src/quicksy/res/values-tr-rTR/strings.xml
similarity index 100%
rename from src/quicksy/res/values-tr-rTR/strings.xml
rename to app/src/quicksy/res/values-tr-rTR/strings.xml
diff --git a/src/quicksy/res/values-uk/strings.xml b/app/src/quicksy/res/values-uk/strings.xml
similarity index 100%
rename from src/quicksy/res/values-uk/strings.xml
rename to app/src/quicksy/res/values-uk/strings.xml
diff --git a/src/quicksy/res/values-vi/strings.xml b/app/src/quicksy/res/values-vi/strings.xml
similarity index 100%
rename from src/quicksy/res/values-vi/strings.xml
rename to app/src/quicksy/res/values-vi/strings.xml
diff --git a/src/quicksy/res/values-zh-rCN/strings.xml b/app/src/quicksy/res/values-zh-rCN/strings.xml
similarity index 100%
rename from src/quicksy/res/values-zh-rCN/strings.xml
rename to app/src/quicksy/res/values-zh-rCN/strings.xml
diff --git a/src/quicksy/res/values-zh-rTW/strings.xml b/app/src/quicksy/res/values-zh-rTW/strings.xml
similarity index 100%
rename from src/quicksy/res/values-zh-rTW/strings.xml
rename to app/src/quicksy/res/values-zh-rTW/strings.xml
diff --git a/src/quicksy/res/values/strings.xml b/app/src/quicksy/res/values/strings.xml
similarity index 100%
rename from src/quicksy/res/values/strings.xml
rename to app/src/quicksy/res/values/strings.xml
diff --git a/src/test/java/im/conversations/android/xmpp/EntityCapabilitiesTest.java b/app/src/test/java/im/conversations/android/xmpp/EntityCapabilitiesTest.java
similarity index 100%
rename from src/test/java/im/conversations/android/xmpp/EntityCapabilitiesTest.java
rename to app/src/test/java/im/conversations/android/xmpp/EntityCapabilitiesTest.java
diff --git a/src/test/java/im/conversations/android/xmpp/PubSubTest.java b/app/src/test/java/im/conversations/android/xmpp/PubSubTest.java
similarity index 100%
rename from src/test/java/im/conversations/android/xmpp/PubSubTest.java
rename to app/src/test/java/im/conversations/android/xmpp/PubSubTest.java
diff --git a/src/test/java/im/conversations/android/xmpp/TimestampTest.java b/app/src/test/java/im/conversations/android/xmpp/TimestampTest.java
similarity index 100%
rename from src/test/java/im/conversations/android/xmpp/TimestampTest.java
rename to app/src/test/java/im/conversations/android/xmpp/TimestampTest.java
diff --git a/src/test/java/im/conversations/android/xmpp/XmlElementReaderTest.java b/app/src/test/java/im/conversations/android/xmpp/XmlElementReaderTest.java
similarity index 100%
rename from src/test/java/im/conversations/android/xmpp/XmlElementReaderTest.java
rename to app/src/test/java/im/conversations/android/xmpp/XmlElementReaderTest.java
diff --git a/build.gradle b/build.gradle
index 7c156fc29..5170846e3 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,318 +1,28 @@
buildscript {
-
ext {
- room_version = "2.5.0"
- navVersion = '2.5.3'
appcompatVersion = "1.6.1"
+ material = "1.8.0"
lifecycleVersion = "2.2.0"
+ navVersion = '2.5.3'
+ roomVersion = "2.5.0"
+ espressoVersion = "3.5.1"
}
repositories {
google()
mavenCentral()
}
+
+
dependencies {
classpath 'com.android.tools.build:gradle:7.4.1'
- classpath "com.diffplug.spotless:spotless-plugin-gradle:6.13.0"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$navVersion"
+ classpath "com.diffplug.spotless:spotless-plugin-gradle:6.13.0"
}
}
-
-apply plugin: 'com.android.application'
-apply plugin: "com.diffplug.spotless"
-apply plugin: "androidx.navigation.safeargs"
-
-
-repositories {
- google()
- mavenCentral()
- jcenter()
-}
-
-configurations {
- playstoreImplementation
- freeImplementation
- conversationsFreeImplementation
- conversationsPlaystorImplementation
- conversationsPlaystoreImplementation
- quicksyPlaystoreImplementation
- quicksyPlaystoreImplementation
- quicksyFreeImplementation
- quicksyImplementation
-}
-
-spotless {
- ratchetFrom '2.12.2'
- java {
- target '**/*.java'
- googleJavaFormat('1.8').aosp().reflowLongStrings()
+allprojects {
+ repositories {
+ google()
+ mavenCentral()
}
-}
-
-dependencies {
-
- // Conversations 3.0 dependencies
-
- implementation project(':libs:annotation')
- annotationProcessor project(':libs:annotation-processor')
-
- coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.8'
-
- implementation "androidx.appcompat:appcompat:$rootProject.ext.appcompatVersion"
-
- implementation "androidx.lifecycle:lifecycle-extensions:$rootProject.ext.lifecycleVersion"
-
- implementation "androidx.room:room-runtime:$room_version"
- annotationProcessor "androidx.room:room-compiler:$room_version"
- implementation "androidx.room:room-guava:$room_version"
-
- implementation "androidx.navigation:navigation-fragment:$rootProject.ext.navVersion"
- implementation "androidx.navigation:navigation-ui:$rootProject.ext.navVersion"
-
- implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
-
-
- implementation "androidx.security:security-crypto:1.0.0"
-
-
- implementation 'org.slf4j:slf4j-api:1.7.36'
- implementation 'com.github.tony19:logback-android:2.0.1'
-
- testImplementation 'junit:junit:4.13.2'
- testImplementation 'org.robolectric:robolectric:4.9'
- androidTestImplementation 'androidx.test.ext:junit:1.1.5'
- androidTestImplementation 'androidx.test:runner:1.5.2'
-
-
- // legacy dependencies. Ideally everything below should be carefully reviewed and eventually moved up
-
-
- implementation 'androidx.viewpager:viewpager:1.0.0'
-
- playstoreImplementation('com.google.firebase:firebase-messaging:23.1.1') {
- 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'
- }
- conversationsPlaystoreImplementation("com.android.installreferrer:installreferrer:2.2")
- quicksyPlaystoreImplementation 'com.google.android.gms:play-services-auth-api-phone:18.0.1'
- implementation 'org.sufficientlysecure:openpgp-api:10.0'
- implementation 'com.theartofdev.edmodo:android-image-cropper:2.8.0'
- implementation 'androidx.exifinterface:exifinterface:1.3.6'
- implementation 'androidx.cardview:cardview:1.0.0'
- implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
- implementation 'com.google.android.material:material:1.8.0'
-
- implementation "androidx.emoji2:emoji2:1.2.0"
- freeImplementation "androidx.emoji2:emoji2-bundled:1.2.0"
-
- implementation 'org.bouncycastle:bcmail-jdk15on:1.64'
- //zxing stopped supporting Java 7 so we have to stick with 3.3.3
- //https://github.com/zxing/zxing/issues/1170
- implementation 'com.google.zxing:core:3.3.3'
- implementation 'de.measite.minidns:minidns-hla:0.2.4'
- implementation 'me.leolin:ShortcutBadger:1.1.22@aar'
- implementation 'org.whispersystems:signal-protocol-java:2.6.2'
- implementation 'com.makeramen:roundedimageview:2.3.0'
- implementation "com.wefika:flowlayout:0.4.1"
- //noinspection GradleDependency
- implementation 'com.otaliastudios:transcoder:0.9.1'
-
- implementation 'org.jxmpp:jxmpp-jid:1.0.3'
- implementation 'org.osmdroid:osmdroid-android:6.1.11'
- implementation 'org.hsluv:hsluv:0.2'
- implementation 'org.conscrypt:conscrypt-android:2.5.2'
- implementation 'me.drakeet.support:toastcompat:1.1.0'
- implementation "com.leinardi.android:speed-dial:3.2.0"
-
- implementation "com.squareup.retrofit2:retrofit:2.9.0"
- implementation "com.squareup.retrofit2:converter-gson:2.9.0"
- implementation "com.squareup.okhttp3:okhttp:4.10.0"
-
- implementation 'com.google.guava:guava:31.1-android'
- quicksyImplementation 'io.michaelrocks:libphonenumber-android:8.12.49'
- implementation 'im.conversations.webrtc:webrtc-android:104.0.0'
-}
-
-ext {
- preDexEnabled = System.getProperty("pre-dex", "true")
- abiCodes = ['armeabi-v7a': 1, 'x86': 2, 'x86_64': 3, 'arm64-v8a': 4]
-}
-
-android {
- namespace 'eu.siacs.conversations'
- compileSdkVersion 33
-
- defaultConfig {
- minSdkVersion 23
- targetSdkVersion 33
- versionCode 1
- versionName "3.0.0-alpha"
- archivesBaseName += "-$versionName"
- applicationId "im.conversations.android"
- resValue "string", "applicationId", applicationId
- def appName = "Conversations"
- resValue "string", "app_name", appName
- buildConfigField "String", "APP_NAME", "\"$appName\""
-
- javaCompileOptions {
- annotationProcessorOptions {
- arguments += ["room.schemaLocation": "$projectDir/schemas".toString()]
- }
- }
-
- testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
- testInstrumentationRunnerArguments clearPackageData: 'true'
-
- }
-
- testOptions {
- unitTests {
- includeAndroidResources = false
- }
- }
-
- splits {
- abi {
- universalApk true
- enable true
- }
- }
-
- configurations {
- implementation.exclude group: 'org.jetbrains' , module:'annotations'
- }
-
- dataBinding {
- enabled true
- }
-
- compileOptions {
- coreLibraryDesugaringEnabled true
- sourceCompatibility JavaVersion.VERSION_11
- targetCompatibility JavaVersion.VERSION_11
- }
-
-
- flavorDimensions("mode", "distribution")
-
- productFlavors {
-
- quicksy {
- dimension "mode"
- applicationId = "im.quicksy.client"
- resValue "string", "applicationId", applicationId
-
- def appName = "Quicksy"
- resValue "string", "app_name", appName
- buildConfigField "String", "APP_NAME", "\"$appName\""
- }
-
- conversations {
- dimension "mode"
- }
-
- playstore {
- dimension "distribution"
- versionNameSuffix "+playstore"
- }
- free {
- dimension "distribution"
- versionNameSuffix "+free"
- }
- }
-
- sourceSets {
- quicksyFree {
- java {
- srcDir 'src/quicksyFree/java'
- }
- }
- quicksyPlaystore {
- java {
- srcDir 'src/quicksyPlaystore/java'
- }
- res {
- srcDir 'src/quicksyPlaystore/res'
- }
- }
- conversationsFree {
- java {
- srcDir 'src/conversationsFree/java'
- }
- }
- conversationsPlaystore {
- java {
- srcDir 'src/conversationsPlaystore/java'
- }
- res {
- srcDir 'src/conversationsPlaystore/res'
- }
- }
- }
-
- buildTypes {
- release {
- shrinkResources true
- minifyEnabled true
- proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
- }
- debug {
- //useProguard false
- //shrinkResources true
- //minifyEnabled true
- //proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
- }
- }
-
-
- if (new File("signing.properties").exists()) {
- Properties props = new Properties()
- props.load(new FileInputStream(file("signing.properties")))
-
- signingConfigs {
- release {
- storeFile file(props['keystore'])
- storePassword props['keystore.password']
- keyAlias props['keystore.alias']
- keyPassword props['keystore.password']
- }
- }
- buildTypes.release.signingConfig = signingConfigs.release
- }
-
-
- subprojects {
-
- afterEvaluate {
- if (getPlugins().hasPlugin('android') ||
- getPlugins().hasPlugin('android-library')) {
-
- configure(android.lintOptions) {
- disable 'AndroidGradlePluginVersion', 'MissingTranslation'
- }
- }
-
- }
- }
- packagingOptions {
- resources {
- excludes += ['META-INF/BCKEY.DSA', 'META-INF/BCKEY.SF']
- }
- }
- lint {
- disable 'MissingTranslation', 'InvalidPackage', 'AppCompatResource'
- }
-
- android.applicationVariants.all { variant ->
- variant.outputs.each { output ->
- def baseAbiVersionCode = project.ext.abiCodes.get(output.getFilter(com.android.build.OutputFile.ABI))
- if (baseAbiVersionCode != null) {
- output.versionCodeOverride = (100 * project.android.defaultConfig.versionCode) + baseAbiVersionCode
- } else {
- output.versionCodeOverride = 100 * project.android.defaultConfig.versionCode
- }
- }
-
- }
-}
+}
\ No newline at end of file
diff --git a/gradle.properties b/gradle.properties
index 431e485f2..a03b35489 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,3 +1,21 @@
+# Project-wide Gradle settings.
+# IDE (e.g. Android Studio) users:
+# Gradle settings configured through the IDE *will override*
+# any settings specified in this file.
+# For more details on how to configure your build environment visit
+# http://www.gradle.org/docs/current/userguide/build_environment.html
+# Specifies the JVM arguments used for the daemon process.
+# The setting is particularly useful for tweaking memory settings.
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+# When configured, Gradle will run in incubating parallel mode.
+# This option should only be used with decoupled projects. More details, visit
+# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
+# org.gradle.parallel=true
+# AndroidX package structure to make it clearer which packages are bundled with the
+# Android operating system, and which are packaged with your app's APK
+# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
-android.enableJetifier=true
-org.gradle.jvmargs=-Xmx4096m
+# Enables namespacing of each library's R class so that its R class includes only the
+# resources declared in the library itself and none from the library's dependencies,
+# thereby reducing the size of the R class for that library
+android.nonTransitiveRClass=true
diff --git a/libs/annotation-processor/build.gradle b/libs/annotation-processor/build.gradle
deleted file mode 100644
index 3bad25d2b..000000000
--- a/libs/annotation-processor/build.gradle
+++ /dev/null
@@ -1,21 +0,0 @@
-plugins {
- id 'java-library'
-}
-
-repositories {
- google()
- mavenCentral()
-}
-
-java {
- sourceCompatibility = JavaVersion.VERSION_11
- targetCompatibility = JavaVersion.VERSION_11
-}
-dependencies {
-
- implementation project(':libs:annotation')
-
- annotationProcessor 'com.google.auto.service:auto-service:1.0-rc5'
- compileOnly 'com.google.auto.service:auto-service:1.0-rc5'
-
-}
\ No newline at end of file
diff --git a/libs/annotation/build.gradle b/libs/annotation/build.gradle
deleted file mode 100644
index e493c42ff..000000000
--- a/libs/annotation/build.gradle
+++ /dev/null
@@ -1,8 +0,0 @@
-plugins {
- id 'java-library'
-}
-
-java {
- sourceCompatibility = JavaVersion.VERSION_1_7
- targetCompatibility = JavaVersion.VERSION_1_7
-}
\ No newline at end of file
diff --git a/proguard-rules.pro b/proguard-rules.pro
deleted file mode 100644
index b9bab8865..000000000
--- a/proguard-rules.pro
+++ /dev/null
@@ -1,74 +0,0 @@
--dontobfuscate
-
--keep class eu.siacs.conversations.**
-
--keep class org.whispersystems.**
-
--keep class com.kyleduo.switchbutton.Configuration
-
--keep class com.soundcloud.android.crop.**
-
--keep class com.google.android.gms.**
-
--keep class org.openintents.openpgp.*
--keep class org.webrtc.** { *; }
-
-# Logger
--keep class org.slf4j.** {*;}
--keep class ch.qos.** {*;}
-
--dontwarn javax.mail.internet.MimeMessage
--dontwarn javax.mail.internet.MimeBodyPart
--dontwarn javax.mail.internet.SharedInputStream
--dontwarn javax.activation.DataContentHandler
--dontwarn org.bouncycastle.mail.**
--dontwarn org.bouncycastle.x509.util.LDAPStoreHelper
--dontwarn org.bouncycastle.jce.provider.X509LDAPCertStoreSpi
--dontwarn org.bouncycastle.cert.dane.**
--dontwarn rocks.xmpp.addr.**
--dontwarn com.google.firebase.analytics.connector.AnalyticsConnector
--dontwarn java.lang.**
--dontwarn javax.lang.**
-
--dontwarn com.android.org.conscrypt.SSLParametersImpl
--dontwarn org.apache.harmony.xnet.provider.jsse.SSLParametersImpl
--dontwarn org.bouncycastle.jsse.BCSSLParameters
--dontwarn org.bouncycastle.jsse.BCSSLSocket
--dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider
--dontwarn org.openjsse.javax.net.ssl.SSLParameters
--dontwarn org.openjsse.javax.net.ssl.SSLSocket
--dontwarn org.openjsse.net.ssl.OpenJSSE
-
--keepclassmembers class eu.siacs.conversations.http.services.** {
- !transient ;
-}
-
-# Retrofit does reflection on generic parameters. InnerClasses is required to use Signature and
-# EnclosingMethod is required to use InnerClasses.
--keepattributes Signature, InnerClasses, EnclosingMethod
-
-# Retrofit does reflection on method and parameter annotations.
--keepattributes RuntimeVisibleAnnotations, RuntimeVisibleParameterAnnotations
-
-# Retain service method parameters when optimizing.
--keepclassmembers,allowshrinking,allowobfuscation interface * {
- @retrofit2.http.* ;
-}
-
-# Ignore annotation used for build tooling.
--dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
-
-# Ignore JSR 305 annotations for embedding nullability information.
--dontwarn javax.annotation.**
-
-# Guarded by a NoClassDefFoundError try/catch and only used when on the classpath.
--dontwarn kotlin.Unit
-
-# Top-level functions that can only be used by Kotlin.
--dontwarn retrofit2.KotlinExtensions
--dontwarn retrofit2.KotlinExtensions$*
-
-# With R8 full mode, it sees no subtypes of Retrofit interfaces since they are created with a Proxy
-# and replaces all potential values with null. Explicitly keeping the interfaces prevents this.
--if interface * { @retrofit2.http.* ; }
--keep,allowobfuscation interface <1>
diff --git a/settings.gradle b/settings.gradle
index a08420a89..c9722936c 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,3 +1,4 @@
-include ':libs:annotation'
-include ':libs:annotation-processor'
-rootProject.name = 'Conversations'
+rootProject.name = "Conversations"
+include ':app'
+include ':annotation'
+include ':annotation-processor'
diff --git a/src/conversations/AndroidManifest.xml b/src/conversations/AndroidManifest.xml
deleted file mode 100644
index c79e4e265..000000000
--- a/src/conversations/AndroidManifest.xml
+++ /dev/null
@@ -1,80 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/conversations/java/eu/siacs/conversations/entities/AccountConfiguration.java b/src/conversations/java/eu/siacs/conversations/entities/AccountConfiguration.java
deleted file mode 100644
index 65b6804f9..000000000
--- a/src/conversations/java/eu/siacs/conversations/entities/AccountConfiguration.java
+++ /dev/null
@@ -1,50 +0,0 @@
-package eu.siacs.conversations.entities;
-
-import com.google.common.base.Preconditions;
-import com.google.gson.Gson;
-import com.google.gson.GsonBuilder;
-import com.google.gson.JsonSyntaxException;
-import com.google.gson.annotations.SerializedName;
-
-import eu.siacs.conversations.xmpp.Jid;
-
-public class AccountConfiguration {
-
- private static final Gson GSON = new GsonBuilder().create();
-
- public Protocol protocol;
- public String address;
- public String password;
-
- public Jid getJid() {
- return Jid.ofEscaped(address);
- }
-
- public static AccountConfiguration parse(final String input) {
- final AccountConfiguration c;
- try {
- c = GSON.fromJson(input, AccountConfiguration.class);
- } catch (JsonSyntaxException e) {
- throw new IllegalArgumentException("Not a valid JSON string", e);
- }
- Preconditions.checkArgument(
- c.protocol == Protocol.XMPP,
- "Protocol must be XMPP"
- );
- Preconditions.checkArgument(
- c.address != null && c.getJid().isBareJid() && !c.getJid().isDomainJid(),
- "Invalid XMPP address"
- );
- Preconditions.checkArgument(
- c.password != null && c.password.length() > 0,
- "No password specified"
- );
- return c;
- }
-
- public enum Protocol {
- @SerializedName("xmpp") XMPP,
- }
-
-}
-
diff --git a/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java b/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java
deleted file mode 100644
index c118d7375..000000000
--- a/src/conversations/java/eu/siacs/conversations/services/ImportBackupService.java
+++ /dev/null
@@ -1,394 +0,0 @@
-package eu.siacs.conversations.services;
-
-import static eu.siacs.conversations.utils.Compatibility.s;
-
-import android.app.Notification;
-import android.app.NotificationManager;
-import android.app.PendingIntent;
-import android.app.Service;
-import android.content.Context;
-import android.content.Intent;
-import android.database.Cursor;
-import android.database.sqlite.SQLiteDatabase;
-import android.net.Uri;
-import android.os.Binder;
-import android.os.IBinder;
-import android.provider.OpenableColumns;
-import android.util.Log;
-
-import androidx.core.app.NotificationCompat;
-import androidx.core.app.NotificationManagerCompat;
-
-import com.google.common.base.Charsets;
-import com.google.common.base.Stopwatch;
-import com.google.common.io.CountingInputStream;
-
-import org.bouncycastle.crypto.engines.AESEngine;
-import org.bouncycastle.crypto.io.CipherInputStream;
-import org.bouncycastle.crypto.modes.AEADBlockCipher;
-import org.bouncycastle.crypto.modes.GCMBlockCipher;
-import org.bouncycastle.crypto.params.AEADParameters;
-import org.bouncycastle.crypto.params.KeyParameter;
-
-import java.io.BufferedReader;
-import java.io.DataInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.WeakHashMap;
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.zip.GZIPInputStream;
-import java.util.zip.ZipException;
-
-import javax.crypto.BadPaddingException;
-
-import eu.siacs.conversations.Config;
-import eu.siacs.conversations.R;
-import eu.siacs.conversations.persistance.DatabaseBackend;
-import eu.siacs.conversations.persistance.FileBackend;
-import eu.siacs.conversations.ui.ManageAccountActivity;
-import eu.siacs.conversations.utils.BackupFileHeader;
-import eu.siacs.conversations.utils.SerialSingleThreadExecutor;
-import eu.siacs.conversations.xmpp.Jid;
-
-public class ImportBackupService extends Service {
-
- private static final int NOTIFICATION_ID = 21;
- private static final AtomicBoolean running = new AtomicBoolean(false);
- private final ImportBackupServiceBinder binder = new ImportBackupServiceBinder();
- private final SerialSingleThreadExecutor executor = new SerialSingleThreadExecutor(getClass().getSimpleName());
- private final Set mOnBackupProcessedListeners = Collections.newSetFromMap(new WeakHashMap<>());
- private DatabaseBackend mDatabaseBackend;
- private NotificationManager notificationManager;
-
- private static int count(String input, char c) {
- int count = 0;
- for (char aChar : input.toCharArray()) {
- if (aChar == c) {
- ++count;
- }
- }
- return count;
- }
-
- @Override
- public void onCreate() {
- mDatabaseBackend = DatabaseBackend.getInstance(getBaseContext());
- notificationManager = (android.app.NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
- }
-
- @Override
- public int onStartCommand(Intent intent, int flags, int startId) {
- if (intent == null) {
- return START_NOT_STICKY;
- }
- final String password = intent.getStringExtra("password");
- final Uri data = intent.getData();
- final Uri uri;
- if (data == null) {
- final String file = intent.getStringExtra("file");
- uri = file == null ? null : Uri.fromFile(new File(file));
- } else {
- uri = data;
- }
-
- if (password == null || password.isEmpty() || uri == null) {
- return START_NOT_STICKY;
- }
- if (running.compareAndSet(false, true)) {
- executor.execute(() -> {
- startForegroundService();
- final boolean success = importBackup(uri, password);
- stopForeground(true);
- running.set(false);
- if (success) {
- notifySuccess();
- }
- stopSelf();
- });
- } else {
- Log.d(Config.LOGTAG, "backup already running");
- }
- return START_NOT_STICKY;
- }
-
- public boolean getLoadingState() {
- return running.get();
- }
-
- public void loadBackupFiles(final OnBackupFilesLoaded onBackupFilesLoaded) {
- executor.execute(() -> {
- final List accounts = mDatabaseBackend.getAccountJids(false);
- final ArrayList backupFiles = new ArrayList<>();
- final Set apps = new HashSet<>(Arrays.asList("Conversations", "Quicksy", getString(R.string.app_name)));
- final List directories = new ArrayList<>();
- for (final String app : apps) {
- directories.add(FileBackend.getLegacyBackupDirectory(app));
- }
- directories.add(FileBackend.getBackupDirectory(this));
- for (final File directory : directories) {
- if (!directory.exists() || !directory.isDirectory()) {
- Log.d(Config.LOGTAG, "directory not found: " + directory.getAbsolutePath());
- continue;
- }
- final File[] files = directory.listFiles();
- if (files == null) {
- continue;
- }
- for (final File file : files) {
- if (file.isFile() && file.getName().endsWith(".ceb")) {
- try {
- final BackupFile backupFile = BackupFile.read(file);
- if (accounts.contains(backupFile.getHeader().getJid())) {
- Log.d(Config.LOGTAG, "skipping backup for " + backupFile.getHeader().getJid());
- } else {
- backupFiles.add(backupFile);
- }
- } catch (IOException | IllegalArgumentException e) {
- Log.d(Config.LOGTAG, "unable to read backup file ", e);
- }
- }
- }
- }
- Collections.sort(backupFiles, (a, b) -> a.header.getJid().toString().compareTo(b.header.getJid().toString()));
- onBackupFilesLoaded.onBackupFilesLoaded(backupFiles);
- });
- }
-
- private void startForegroundService() {
- startForeground(NOTIFICATION_ID, createImportBackupNotification(1, 0));
- }
-
- private void updateImportBackupNotification(final long total, final long current) {
- final int max;
- final int progress;
- if (total == 0) {
- max = 1;
- progress = 0;
- } else {
- max = 100;
- progress = (int) (current * 100 / total);
- }
- final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
- try {
- notificationManager.notify(NOTIFICATION_ID, createImportBackupNotification(max, progress));
- } catch (final RuntimeException e) {
- Log.d(Config.LOGTAG, "unable to make notification", e);
- }
- }
-
- private Notification createImportBackupNotification(final int max, final int progress) {
- NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(getBaseContext(), "backup");
- mBuilder.setContentTitle(getString(R.string.restoring_backup))
- .setSmallIcon(R.drawable.ic_unarchive_white_24dp)
- .setProgress(max, progress, max == 1 && progress == 0);
- return mBuilder.build();
- }
-
- private boolean importBackup(final Uri uri, final String password) {
- Log.d(Config.LOGTAG, "importing backup from " + uri);
- final Stopwatch stopwatch = Stopwatch.createStarted();
- try {
- final SQLiteDatabase db = mDatabaseBackend.getWritableDatabase();
- final InputStream inputStream;
- final String path = uri.getPath();
- final long fileSize;
- if ("file".equals(uri.getScheme()) && path != null) {
- final File file = new File(path);
- inputStream = new FileInputStream(file);
- fileSize = file.length();
- } else {
- final Cursor returnCursor = getContentResolver().query(uri, null, null, null, null);
- if (returnCursor == null) {
- fileSize = 0;
- } else {
- returnCursor.moveToFirst();
- fileSize = returnCursor.getLong(returnCursor.getColumnIndex(OpenableColumns.SIZE));
- returnCursor.close();
- }
- inputStream = getContentResolver().openInputStream(uri);
- }
- if (inputStream == null) {
- synchronized (mOnBackupProcessedListeners) {
- for (final OnBackupProcessed l : mOnBackupProcessedListeners) {
- l.onBackupRestoreFailed();
- }
- }
- return false;
- }
- final CountingInputStream countingInputStream = new CountingInputStream(inputStream);
- final DataInputStream dataInputStream = new DataInputStream(countingInputStream);
- final BackupFileHeader backupFileHeader = BackupFileHeader.read(dataInputStream);
- Log.d(Config.LOGTAG, backupFileHeader.toString());
-
- if (mDatabaseBackend.getAccountJids(false).contains(backupFileHeader.getJid())) {
- synchronized (mOnBackupProcessedListeners) {
- for (OnBackupProcessed l : mOnBackupProcessedListeners) {
- l.onAccountAlreadySetup();
- }
- }
- return false;
- }
-
- final byte[] key = ExportBackupService.getKey(password, backupFileHeader.getSalt());
-
- final AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine());
- cipher.init(false, new AEADParameters(new KeyParameter(key), 128, backupFileHeader.getIv()));
- final CipherInputStream cipherInputStream = new CipherInputStream(countingInputStream, cipher);
-
- final GZIPInputStream gzipInputStream = new GZIPInputStream(cipherInputStream);
- final BufferedReader reader = new BufferedReader(new InputStreamReader(gzipInputStream, Charsets.UTF_8));
- db.beginTransaction();
- String line;
- StringBuilder multiLineQuery = null;
- while ((line = reader.readLine()) != null) {
- int count = count(line, '\'');
- if (multiLineQuery != null) {
- multiLineQuery.append('\n');
- multiLineQuery.append(line);
- if (count % 2 == 1) {
- db.execSQL(multiLineQuery.toString());
- multiLineQuery = null;
- updateImportBackupNotification(fileSize, countingInputStream.getCount());
- }
- } else {
- if (count % 2 == 0) {
- db.execSQL(line);
- updateImportBackupNotification(fileSize, countingInputStream.getCount());
- } else {
- multiLineQuery = new StringBuilder(line);
- }
- }
- }
- db.setTransactionSuccessful();
- db.endTransaction();
- final Jid jid = backupFileHeader.getJid();
- final Cursor countCursor = db.rawQuery("select count(messages.uuid) from messages join conversations on conversations.uuid=messages.conversationUuid join accounts on conversations.accountUuid=accounts.uuid where accounts.username=? and accounts.server=?", new String[]{jid.getEscapedLocal(), jid.getDomain().toEscapedString()});
- countCursor.moveToFirst();
- final int count = countCursor.getInt(0);
- Log.d(Config.LOGTAG, String.format("restored %d messages in %s", count, stopwatch.stop().toString()));
- countCursor.close();
- stopBackgroundService();
- synchronized (mOnBackupProcessedListeners) {
- for (OnBackupProcessed l : mOnBackupProcessedListeners) {
- l.onBackupRestored();
- }
- }
- return true;
- } catch (final Exception e) {
- final Throwable throwable = e.getCause();
- final boolean reasonWasCrypto = throwable instanceof BadPaddingException || e instanceof ZipException;
- synchronized (mOnBackupProcessedListeners) {
- for (OnBackupProcessed l : mOnBackupProcessedListeners) {
- if (reasonWasCrypto) {
- l.onBackupDecryptionFailed();
- } else {
- l.onBackupRestoreFailed();
- }
- }
- }
- Log.d(Config.LOGTAG, "error restoring backup " + uri, e);
- return false;
- }
- }
-
- private void notifySuccess() {
- NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(getBaseContext(), "backup");
- mBuilder.setContentTitle(getString(R.string.notification_restored_backup_title))
- .setContentText(getString(R.string.notification_restored_backup_subtitle))
- .setAutoCancel(true)
- .setContentIntent(PendingIntent.getActivity(this, 145, new Intent(this, ManageAccountActivity.class), s()
- ? PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT
- : PendingIntent.FLAG_UPDATE_CURRENT))
- .setSmallIcon(R.drawable.ic_unarchive_white_24dp);
- notificationManager.notify(NOTIFICATION_ID, mBuilder.build());
- }
-
- private void stopBackgroundService() {
- Intent intent = new Intent(this, XmppConnectionService.class);
- stopService(intent);
- }
-
- public void removeOnBackupProcessedListener(OnBackupProcessed listener) {
- synchronized (mOnBackupProcessedListeners) {
- mOnBackupProcessedListeners.remove(listener);
- }
- }
-
- public void addOnBackupProcessedListener(OnBackupProcessed listener) {
- synchronized (mOnBackupProcessedListeners) {
- mOnBackupProcessedListeners.add(listener);
- }
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- return this.binder;
- }
-
- public interface OnBackupFilesLoaded {
- void onBackupFilesLoaded(List files);
- }
-
- public interface OnBackupProcessed {
- void onBackupRestored();
-
- void onBackupDecryptionFailed();
-
- void onBackupRestoreFailed();
-
- void onAccountAlreadySetup();
- }
-
- public static class BackupFile {
- private final Uri uri;
- private final BackupFileHeader header;
-
- private BackupFile(Uri uri, BackupFileHeader header) {
- this.uri = uri;
- this.header = header;
- }
-
- private static BackupFile read(File file) throws IOException {
- final FileInputStream fileInputStream = new FileInputStream(file);
- final DataInputStream dataInputStream = new DataInputStream(fileInputStream);
- BackupFileHeader backupFileHeader = BackupFileHeader.read(dataInputStream);
- fileInputStream.close();
- return new BackupFile(Uri.fromFile(file), backupFileHeader);
- }
-
- public static BackupFile read(final Context context, final Uri uri) throws IOException {
- final InputStream inputStream = context.getContentResolver().openInputStream(uri);
- if (inputStream == null) {
- throw new FileNotFoundException();
- }
- final DataInputStream dataInputStream = new DataInputStream(inputStream);
- BackupFileHeader backupFileHeader = BackupFileHeader.read(dataInputStream);
- inputStream.close();
- return new BackupFile(uri, backupFileHeader);
- }
-
- public BackupFileHeader getHeader() {
- return header;
- }
-
- public Uri getUri() {
- return uri;
- }
- }
-
- public class ImportBackupServiceBinder extends Binder {
- public ImportBackupService getService() {
- return ImportBackupService.this;
- }
- }
-}
\ No newline at end of file
diff --git a/src/conversations/java/eu/siacs/conversations/services/QuickConversationsService.java b/src/conversations/java/eu/siacs/conversations/services/QuickConversationsService.java
deleted file mode 100644
index b2a0d17f4..000000000
--- a/src/conversations/java/eu/siacs/conversations/services/QuickConversationsService.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package eu.siacs.conversations.services;
-
-import android.content.Intent;
-import android.util.Log;
-
-import eu.siacs.conversations.Config;
-
-public class QuickConversationsService extends AbstractQuickConversationsService {
-
- QuickConversationsService(XmppConnectionService xmppConnectionService) {
- super(xmppConnectionService);
- }
-
- @Override
- public void considerSync() {
-
- }
-
- @Override
- public void signalAccountStateChange() {
-
- }
-
- @Override
- public boolean isSynchronizing() {
- return false;
- }
-
- @Override
- public void considerSyncBackground(boolean force) {
-
- }
-
- @Override
- public void handleSmsReceived(Intent intent) {
- Log.d(Config.LOGTAG,"ignoring received SMS");
- }
-}
\ No newline at end of file
diff --git a/src/conversations/java/eu/siacs/conversations/ui/EasyOnboardingInviteActivity.java b/src/conversations/java/eu/siacs/conversations/ui/EasyOnboardingInviteActivity.java
deleted file mode 100644
index fea92401b..000000000
--- a/src/conversations/java/eu/siacs/conversations/ui/EasyOnboardingInviteActivity.java
+++ /dev/null
@@ -1,151 +0,0 @@
-package eu.siacs.conversations.ui;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.graphics.Bitmap;
-import android.graphics.Point;
-import android.os.Bundle;
-import android.util.Log;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-import android.widget.Toast;
-
-import androidx.databinding.DataBindingUtil;
-
-import com.google.common.base.Strings;
-
-import eu.siacs.conversations.Config;
-import eu.siacs.conversations.R;
-import eu.siacs.conversations.databinding.ActivityEasyInviteBinding;
-import eu.siacs.conversations.entities.Account;
-import eu.siacs.conversations.services.BarcodeProvider;
-import eu.siacs.conversations.utils.EasyOnboardingInvite;
-import eu.siacs.conversations.xmpp.Jid;
-
-public class EasyOnboardingInviteActivity extends XmppActivity implements EasyOnboardingInvite.OnInviteRequested {
-
- private ActivityEasyInviteBinding binding;
-
- private EasyOnboardingInvite easyOnboardingInvite;
-
-
- @Override
- public void onCreate(final Bundle bundle) {
- super.onCreate(bundle);
- this.binding = DataBindingUtil.setContentView(this, R.layout.activity_easy_invite);
- setSupportActionBar(binding.toolbar);
- configureActionBar(getSupportActionBar(), true);
- this.binding.shareButton.setOnClickListener(v -> share());
- if (bundle != null && bundle.containsKey("invite")) {
- this.easyOnboardingInvite = bundle.getParcelable("invite");
- if (this.easyOnboardingInvite != null) {
- showInvite(this.easyOnboardingInvite);
- return;
- }
- }
- this.showLoading();
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- getMenuInflater().inflate(R.menu.easy_onboarding_invite, menu);
- final MenuItem share = menu.findItem(R.id.action_share);
- share.setVisible(easyOnboardingInvite != null);
- return super.onCreateOptionsMenu(menu);
- }
-
- public boolean onOptionsItemSelected(MenuItem menuItem) {
- if (menuItem.getItemId() == R.id.action_share) {
- share();
- return true;
- } else {
- return super.onOptionsItemSelected(menuItem);
- }
- }
-
- private void share() {
- final String shareText = getString(
- R.string.easy_invite_share_text,
- easyOnboardingInvite.getDomain(),
- easyOnboardingInvite.getShareableLink()
- );
- final Intent sendIntent = new Intent();
- sendIntent.setAction(Intent.ACTION_SEND);
- sendIntent.putExtra(Intent.EXTRA_TEXT, shareText);
- sendIntent.setType("text/plain");
- startActivity(Intent.createChooser(sendIntent, getString(R.string.share_invite_with)));
- }
-
- @Override
- protected void refreshUiReal() {
- invalidateOptionsMenu();
- if (easyOnboardingInvite != null) {
- showInvite(easyOnboardingInvite);
- } else {
- showLoading();
- }
- }
-
- private void showLoading() {
- this.binding.inProgress.setVisibility(View.VISIBLE);
- this.binding.invite.setVisibility(View.GONE);
- }
-
- private void showInvite(final EasyOnboardingInvite invite) {
- this.binding.inProgress.setVisibility(View.GONE);
- this.binding.invite.setVisibility(View.VISIBLE);
- this.binding.tapToShare.setText(getString(R.string.tap_share_button_send_invite, invite.getDomain()));
- final Point size = new Point();
- getWindowManager().getDefaultDisplay().getSize(size);
- final int width = Math.min(size.x, size.y);
- final Bitmap bitmap = BarcodeProvider.create2dBarcodeBitmap(invite.getShareableLink(), width);
- binding.qrCode.setImageBitmap(bitmap);
- }
-
- @Override
- public void onSaveInstanceState(Bundle bundle) {
- super.onSaveInstanceState(bundle);
- if (easyOnboardingInvite != null) {
- bundle.putParcelable("invite", easyOnboardingInvite);
- }
- }
-
- @Override
- void onBackendConnected() {
- if (easyOnboardingInvite != null) {
- return;
- }
- final Intent launchIntent = getIntent();
- final String accountExtra = launchIntent.getStringExtra(EXTRA_ACCOUNT);
- final Jid jid = accountExtra == null ? null : Jid.ofEscaped(accountExtra);
- if (jid == null) {
- return;
- }
- final Account account = xmppConnectionService.findAccountByJid(jid);
- xmppConnectionService.requestEasyOnboardingInvite(account, this);
- }
-
- public static void launch(final Account account, final Activity context) {
- final Intent intent = new Intent(context, EasyOnboardingInviteActivity.class);
- intent.putExtra(EXTRA_ACCOUNT, account.getJid().asBareJid().toEscapedString());
- context.startActivity(intent);
- }
-
- @Override
- public void inviteRequested(EasyOnboardingInvite invite) {
- this.easyOnboardingInvite = invite;
- Log.d(Config.LOGTAG, "invite requested");
- refreshUi();
- }
-
- @Override
- public void inviteRequestFailed(final String message) {
- runOnUiThread(() -> {
- if (!Strings.isNullOrEmpty(message)) {
- Toast.makeText(this, message, Toast.LENGTH_LONG).show();
- }
- finish();
- });
- }
-}
diff --git a/src/conversations/java/eu/siacs/conversations/ui/ImportBackupActivity.java b/src/conversations/java/eu/siacs/conversations/ui/ImportBackupActivity.java
deleted file mode 100644
index 6e4815159..000000000
--- a/src/conversations/java/eu/siacs/conversations/ui/ImportBackupActivity.java
+++ /dev/null
@@ -1,251 +0,0 @@
-package eu.siacs.conversations.ui;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.net.Uri;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-
-import androidx.appcompat.app.AlertDialog;
-import androidx.core.content.ContextCompat;
-import androidx.databinding.DataBindingUtil;
-
-import com.google.android.material.snackbar.Snackbar;
-
-import java.io.IOException;
-import java.util.List;
-
-import eu.siacs.conversations.Config;
-import eu.siacs.conversations.R;
-import eu.siacs.conversations.databinding.ActivityImportBackupBinding;
-import eu.siacs.conversations.databinding.DialogEnterPasswordBinding;
-import eu.siacs.conversations.services.ImportBackupService;
-import eu.siacs.conversations.ui.adapter.BackupFileAdapter;
-import eu.siacs.conversations.ui.util.SettingsUtils;
-import eu.siacs.conversations.utils.ThemeHelper;
-
-public class ImportBackupActivity extends ActionBarActivity implements ServiceConnection, ImportBackupService.OnBackupFilesLoaded, BackupFileAdapter.OnItemClickedListener, ImportBackupService.OnBackupProcessed {
-
- private ActivityImportBackupBinding binding;
-
- private BackupFileAdapter backupFileAdapter;
- private ImportBackupService service;
-
- private boolean mLoadingState = false;
-
- private int mTheme;
-
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- this.mTheme = ThemeHelper.find(this);
- setTheme(this.mTheme);
- super.onCreate(savedInstanceState);
- binding = DataBindingUtil.setContentView(this, R.layout.activity_import_backup);
- setSupportActionBar(binding.toolbar);
- setLoadingState(savedInstanceState != null && savedInstanceState.getBoolean("loading_state", false));
- this.backupFileAdapter = new BackupFileAdapter();
- this.binding.list.setAdapter(this.backupFileAdapter);
- this.backupFileAdapter.setOnItemClickedListener(this);
- }
-
- @Override
- protected void onResume(){
- super.onResume();
- SettingsUtils.applyScreenshotPreventionSetting(this);
- }
-
- @Override
- public boolean onCreateOptionsMenu(final Menu menu) {
- getMenuInflater().inflate(R.menu.import_backup, menu);
- final MenuItem openBackup = menu.findItem(R.id.action_open_backup_file);
- openBackup.setVisible(!this.mLoadingState);
- return true;
- }
-
- @Override
- public void onSaveInstanceState(Bundle bundle) {
- bundle.putBoolean("loading_state", this.mLoadingState);
- super.onSaveInstanceState(bundle);
- }
-
- @Override
- public void onStart() {
- super.onStart();
- final int theme = ThemeHelper.find(this);
- if (this.mTheme != theme) {
- recreate();
- } else {
- bindService(new Intent(this, ImportBackupService.class), this, Context.BIND_AUTO_CREATE);
- }
- final Intent intent = getIntent();
- if (intent != null && Intent.ACTION_VIEW.equals(intent.getAction()) && !this.mLoadingState) {
- Uri uri = intent.getData();
- if (uri != null) {
- openBackupFileFromUri(uri, true);
- }
- }
- }
-
- @Override
- public void onStop() {
- super.onStop();
- if (this.service != null) {
- this.service.removeOnBackupProcessedListener(this);
- }
- unbindService(this);
- }
-
- @Override
- public void onServiceConnected(ComponentName name, IBinder service) {
- ImportBackupService.ImportBackupServiceBinder binder = (ImportBackupService.ImportBackupServiceBinder) service;
- this.service = binder.getService();
- this.service.addOnBackupProcessedListener(this);
- setLoadingState(this.service.getLoadingState());
- this.service.loadBackupFiles(this);
- }
-
- @Override
- public void onServiceDisconnected(ComponentName name) {
- this.service = null;
- }
-
- @Override
- public void onBackupFilesLoaded(final List files) {
- runOnUiThread(() -> backupFileAdapter.setFiles(files));
- }
-
- @Override
- public void onClick(final ImportBackupService.BackupFile backupFile) {
- showEnterPasswordDialog(backupFile, false);
- }
-
- private void openBackupFileFromUri(final Uri uri, final boolean finishOnCancel) {
- try {
- final ImportBackupService.BackupFile backupFile = ImportBackupService.BackupFile.read(this, uri);
- showEnterPasswordDialog(backupFile, finishOnCancel);
- } catch (final IOException | IllegalArgumentException e) {
- Log.d(Config.LOGTAG, "unable to open backup file " + uri, e);
- Snackbar.make(binding.coordinator, R.string.not_a_backup_file, Snackbar.LENGTH_LONG).show();
- } catch (final SecurityException e) {
- Snackbar.make(binding.coordinator, R.string.sharing_application_not_grant_permission, Snackbar.LENGTH_LONG).show();
- }
- }
-
- private void showEnterPasswordDialog(final ImportBackupService.BackupFile backupFile, final boolean finishOnCancel) {
- final DialogEnterPasswordBinding enterPasswordBinding = DataBindingUtil.inflate(LayoutInflater.from(this), R.layout.dialog_enter_password, null, false);
- Log.d(Config.LOGTAG, "attempting to import " + backupFile.getUri());
- enterPasswordBinding.explain.setText(getString(R.string.enter_password_to_restore, backupFile.getHeader().getJid().toString()));
- AlertDialog.Builder builder = new AlertDialog.Builder(this);
- builder.setView(enterPasswordBinding.getRoot());
- builder.setTitle(R.string.enter_password);
- builder.setNegativeButton(R.string.cancel, (dialog, which) -> {
- if (finishOnCancel) {
- finish();
- }
- });
- builder.setPositiveButton(R.string.restore, null);
- builder.setCancelable(false);
- final AlertDialog dialog = builder.create();
- dialog.setOnShowListener((d) -> {
- dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(v -> {
- final String password = enterPasswordBinding.accountPassword.getEditableText().toString();
- if (password.isEmpty()) {
- enterPasswordBinding.accountPasswordLayout.setError(getString(R.string.please_enter_password));
- return;
- }
- final Uri uri = backupFile.getUri();
- Intent intent = new Intent(this, ImportBackupService.class);
- intent.setAction(Intent.ACTION_SEND);
- intent.putExtra("password", password);
- if ("file".equals(uri.getScheme())) {
- intent.putExtra("file", uri.getPath());
- } else {
- intent.setData(uri);
- intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
- }
- setLoadingState(true);
- ContextCompat.startForegroundService(this, intent);
- d.dismiss();
- });
- });
- dialog.show();
- }
-
- private void setLoadingState(final boolean loadingState) {
- binding.coordinator.setVisibility(loadingState ? View.GONE : View.VISIBLE);
- binding.inProgress.setVisibility(loadingState ? View.VISIBLE : View.GONE);
- setTitle(loadingState ? R.string.restoring_backup : R.string.restore_backup);
- configureActionBar(getSupportActionBar(), !loadingState);
- this.mLoadingState = loadingState;
- invalidateOptionsMenu();
- }
-
- @Override
- public void onActivityResult(int requestCode, int resultCode, Intent intent) {
- super.onActivityResult(requestCode, resultCode, intent);
- if (resultCode == RESULT_OK) {
- if (requestCode == 0xbac) {
- openBackupFileFromUri(intent.getData(), false);
- }
- }
- }
-
- @Override
- public void onAccountAlreadySetup() {
- runOnUiThread(() -> {
- setLoadingState(false);
- Snackbar.make(binding.coordinator, R.string.account_already_setup, Snackbar.LENGTH_LONG).show();
- });
- }
-
- @Override
- public void onBackupRestored() {
- runOnUiThread(() -> {
- Intent intent = new Intent(this, ConversationActivity.class);
- intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
- startActivity(intent);
- finish();
- });
- }
-
- @Override
- public void onBackupDecryptionFailed() {
- runOnUiThread(() -> {
- setLoadingState(false);
- Snackbar.make(binding.coordinator, R.string.unable_to_decrypt_backup, Snackbar.LENGTH_LONG).show();
- });
- }
-
- @Override
- public void onBackupRestoreFailed() {
- runOnUiThread(() -> {
- setLoadingState(false);
- Snackbar.make(binding.coordinator, R.string.unable_to_restore_backup, Snackbar.LENGTH_LONG).show();
- });
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- if (item.getItemId() == R.id.action_open_backup_file) {
- openBackupFile();
- return true;
- }
- return super.onOptionsItemSelected(item);
- }
-
- private void openBackupFile() {
- final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
- intent.setType("*/*");
- intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, false);
- intent.addCategory(Intent.CATEGORY_OPENABLE);
- startActivityForResult(Intent.createChooser(intent, getString(R.string.open_backup)), 0xbac);
- }
-}
diff --git a/src/conversations/java/eu/siacs/conversations/ui/MagicCreateActivity.java b/src/conversations/java/eu/siacs/conversations/ui/MagicCreateActivity.java
deleted file mode 100644
index 38761befd..000000000
--- a/src/conversations/java/eu/siacs/conversations/ui/MagicCreateActivity.java
+++ /dev/null
@@ -1,162 +0,0 @@
-package eu.siacs.conversations.ui;
-
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.os.Bundle;
-import android.text.Editable;
-import android.text.TextWatcher;
-import android.view.View;
-import android.widget.Toast;
-
-import androidx.databinding.DataBindingUtil;
-
-import java.security.SecureRandom;
-
-import eu.siacs.conversations.Config;
-import eu.siacs.conversations.R;
-import eu.siacs.conversations.databinding.MagicCreateBinding;
-import eu.siacs.conversations.entities.Account;
-import eu.siacs.conversations.utils.CryptoHelper;
-import eu.siacs.conversations.utils.InstallReferrerUtils;
-import eu.siacs.conversations.xmpp.Jid;
-
-public class MagicCreateActivity extends XmppActivity implements TextWatcher {
-
- public static final String EXTRA_DOMAIN = "domain";
- public static final String EXTRA_PRE_AUTH = "pre_auth";
- public static final String EXTRA_USERNAME = "username";
-
- private MagicCreateBinding binding;
- private String domain;
- private String username;
- private String preAuth;
-
- @Override
- protected void refreshUiReal() {
-
- }
-
- @Override
- void onBackendConnected() {
-
- }
-
- @Override
- public void onStart() {
- super.onStart();
- final int theme = findTheme();
- if (this.mTheme != theme) {
- recreate();
- }
- }
-
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- final Intent data = getIntent();
- this.domain = data == null ? null : data.getStringExtra(EXTRA_DOMAIN);
- this.preAuth = data == null ? null : data.getStringExtra(EXTRA_PRE_AUTH);
- this.username = data == null ? null : data.getStringExtra(EXTRA_USERNAME);
- if (getResources().getBoolean(R.bool.portrait_only)) {
- setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
- }
- super.onCreate(savedInstanceState);
- this.binding = DataBindingUtil.setContentView(this, R.layout.magic_create);
- setSupportActionBar(this.binding.toolbar);
- configureActionBar(getSupportActionBar(), this.domain == null);
- if (username != null && domain != null) {
- binding.title.setText(R.string.your_server_invitation);
- binding.instructions.setText(getString(R.string.magic_create_text_fixed, domain));
- binding.username.setEnabled(false);
- binding.username.setText(this.username);
- updateFullJidInformation(this.username);
- } else if (domain != null) {
- binding.instructions.setText(getString(R.string.magic_create_text_on_x, domain));
- }
- binding.createAccount.setOnClickListener(v -> {
- try {
- final String username = binding.username.getText().toString();
- final Jid jid;
- final boolean fixedUsername;
- if (this.domain != null && this.username != null) {
- fixedUsername = true;
- jid = Jid.ofLocalAndDomainEscaped(this.username, this.domain);
- } else if (this.domain != null) {
- fixedUsername = false;
- jid = Jid.ofLocalAndDomainEscaped(username, this.domain);
- } else {
- fixedUsername = false;
- jid = Jid.ofLocalAndDomainEscaped(username, Config.MAGIC_CREATE_DOMAIN);
- }
- if (!jid.getEscapedLocal().equals(jid.getLocal()) || (this.username == null && username.length() < 3)) {
- binding.username.setError(getString(R.string.invalid_username));
- binding.username.requestFocus();
- } else {
- binding.username.setError(null);
- Account account = xmppConnectionService.findAccountByJid(jid);
- if (account == null) {
- account = new Account(jid, CryptoHelper.createPassword(new SecureRandom()));
- account.setOption(Account.OPTION_REGISTER, true);
- account.setOption(Account.OPTION_DISABLED, true);
- account.setOption(Account.OPTION_MAGIC_CREATE, true);
- account.setOption(Account.OPTION_FIXED_USERNAME, fixedUsername);
- if (this.preAuth != null) {
- account.setKey(Account.KEY_PRE_AUTH_REGISTRATION_TOKEN, this.preAuth);
- }
- xmppConnectionService.createAccount(account);
- }
- Intent intent = new Intent(MagicCreateActivity.this, EditAccountActivity.class);
- intent.putExtra("jid", account.getJid().asBareJid().toString());
- intent.putExtra("init", true);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
- Toast.makeText(MagicCreateActivity.this, R.string.secure_password_generated, Toast.LENGTH_SHORT).show();
- StartConversationActivity.addInviteUri(intent, getIntent());
- startActivity(intent);
- }
- } catch (IllegalArgumentException e) {
- binding.username.setError(getString(R.string.invalid_username));
- binding.username.requestFocus();
- }
- });
- binding.username.addTextChangedListener(this);
- }
-
- @Override
- public void onDestroy() {
- InstallReferrerUtils.markInstallReferrerExecuted(this);
- super.onDestroy();
- }
-
- @Override
- public void beforeTextChanged(CharSequence s, int start, int count, int after) {
-
- }
-
- @Override
- public void onTextChanged(CharSequence s, int start, int before, int count) {
-
- }
-
- @Override
- public void afterTextChanged(final Editable s) {
- updateFullJidInformation(s.toString());
- }
-
- private void updateFullJidInformation(final String username) {
- if (username.trim().isEmpty()) {
- binding.fullJid.setVisibility(View.INVISIBLE);
- } else {
- try {
- binding.fullJid.setVisibility(View.VISIBLE);
- final Jid jid;
- if (this.domain == null) {
- jid = Jid.ofLocalAndDomainEscaped(username, Config.MAGIC_CREATE_DOMAIN);
- } else {
- jid = Jid.ofLocalAndDomainEscaped(username, this.domain);
- }
- binding.fullJid.setText(getString(R.string.your_full_jid_will_be, jid.toEscapedString()));
- } catch (IllegalArgumentException e) {
- binding.fullJid.setVisibility(View.INVISIBLE);
- }
- }
- }
-}
diff --git a/src/conversations/java/eu/siacs/conversations/ui/ManageAccountActivity.java b/src/conversations/java/eu/siacs/conversations/ui/ManageAccountActivity.java
deleted file mode 100644
index 6aecf4b26..000000000
--- a/src/conversations/java/eu/siacs/conversations/ui/ManageAccountActivity.java
+++ /dev/null
@@ -1,429 +0,0 @@
-package eu.siacs.conversations.ui;
-
-import android.content.ActivityNotFoundException;
-import android.content.Intent;
-import android.os.Bundle;
-import android.security.KeyChain;
-import android.security.KeyChainAliasCallback;
-import android.util.Pair;
-import android.view.ContextMenu;
-import android.view.ContextMenu.ContextMenuInfo;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-import android.widget.AdapterView.AdapterContextMenuInfo;
-import android.widget.ListView;
-import android.widget.Toast;
-
-import androidx.annotation.NonNull;
-import androidx.appcompat.app.ActionBar;
-import androidx.appcompat.app.AlertDialog;
-
-import org.openintents.openpgp.util.OpenPgpApi;
-
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import eu.siacs.conversations.Config;
-import eu.siacs.conversations.R;
-import eu.siacs.conversations.entities.Account;
-import eu.siacs.conversations.services.XmppConnectionService;
-import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate;
-import eu.siacs.conversations.ui.adapter.AccountAdapter;
-import eu.siacs.conversations.ui.util.MenuDoubleTabUtil;
-import eu.siacs.conversations.xmpp.Jid;
-import eu.siacs.conversations.xmpp.XmppConnection;
-
-import static eu.siacs.conversations.utils.PermissionUtils.allGranted;
-import static eu.siacs.conversations.utils.PermissionUtils.writeGranted;
-
-public class ManageAccountActivity extends XmppActivity implements OnAccountUpdate, KeyChainAliasCallback, XmppConnectionService.OnAccountCreated, AccountAdapter.OnTglAccountState {
-
- private final String STATE_SELECTED_ACCOUNT = "selected_account";
-
- private static final int REQUEST_IMPORT_BACKUP = 0x63fb;
-
- protected Account selectedAccount = null;
- protected Jid selectedAccountJid = null;
-
- protected final List accountList = new ArrayList<>();
- protected ListView accountListView;
- protected AccountAdapter mAccountAdapter;
- protected AtomicBoolean mInvokedAddAccount = new AtomicBoolean(false);
-
- protected Pair mPostponedActivityResult = null;
-
- @Override
- public void onAccountUpdate() {
- refreshUi();
- }
-
- @Override
- protected void refreshUiReal() {
- synchronized (this.accountList) {
- accountList.clear();
- accountList.addAll(xmppConnectionService.getAccounts());
- }
- ActionBar actionBar = getSupportActionBar();
- if (actionBar != null) {
- actionBar.setHomeButtonEnabled(this.accountList.size() > 0);
- actionBar.setDisplayHomeAsUpEnabled(this.accountList.size() > 0);
- }
- invalidateOptionsMenu();
- mAccountAdapter.notifyDataSetChanged();
- }
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
-
- super.onCreate(savedInstanceState);
-
- setContentView(R.layout.activity_manage_accounts);
- setSupportActionBar(findViewById(R.id.toolbar));
- configureActionBar(getSupportActionBar());
- if (savedInstanceState != null) {
- String jid = savedInstanceState.getString(STATE_SELECTED_ACCOUNT);
- if (jid != null) {
- try {
- this.selectedAccountJid = Jid.ofEscaped(jid);
- } catch (IllegalArgumentException e) {
- this.selectedAccountJid = null;
- }
- }
- }
-
- accountListView = findViewById(R.id.account_list);
- this.mAccountAdapter = new AccountAdapter(this, accountList);
- accountListView.setAdapter(this.mAccountAdapter);
- accountListView.setOnItemClickListener((arg0, view, position, arg3) -> switchToAccount(accountList.get(position)));
- registerForContextMenu(accountListView);
- }
-
- @Override
- protected void onStart() {
- super.onStart();
- final int theme = findTheme();
- if (this.mTheme != theme) {
- recreate();
- }
- }
-
- @Override
- public void onSaveInstanceState(final Bundle savedInstanceState) {
- if (selectedAccount != null) {
- savedInstanceState.putString(STATE_SELECTED_ACCOUNT, selectedAccount.getJid().asBareJid().toEscapedString());
- }
- super.onSaveInstanceState(savedInstanceState);
- }
-
- @Override
- public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
- super.onCreateContextMenu(menu, v, menuInfo);
- ManageAccountActivity.this.getMenuInflater().inflate(
- R.menu.manageaccounts_context, menu);
- AdapterContextMenuInfo acmi = (AdapterContextMenuInfo) menuInfo;
- this.selectedAccount = accountList.get(acmi.position);
- if (this.selectedAccount.isEnabled()) {
- menu.findItem(R.id.mgmt_account_enable).setVisible(false);
- menu.findItem(R.id.mgmt_account_announce_pgp).setVisible(Config.supportOpenPgp());
- } else {
- menu.findItem(R.id.mgmt_account_disable).setVisible(false);
- menu.findItem(R.id.mgmt_account_announce_pgp).setVisible(false);
- menu.findItem(R.id.mgmt_account_publish_avatar).setVisible(false);
- }
- menu.setHeaderTitle(this.selectedAccount.getJid().asBareJid().toEscapedString());
- }
-
- @Override
- void onBackendConnected() {
- if (selectedAccountJid != null) {
- this.selectedAccount = xmppConnectionService.findAccountByJid(selectedAccountJid);
- }
- refreshUiReal();
- if (this.mPostponedActivityResult != null) {
- this.onActivityResult(mPostponedActivityResult.first, RESULT_OK, mPostponedActivityResult.second);
- }
- if (Config.X509_VERIFICATION && this.accountList.size() == 0) {
- if (mInvokedAddAccount.compareAndSet(false, true)) {
- addAccountFromKey();
- }
- }
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- getMenuInflater().inflate(R.menu.manageaccounts, menu);
- MenuItem enableAll = menu.findItem(R.id.action_enable_all);
- MenuItem addAccount = menu.findItem(R.id.action_add_account);
- MenuItem addAccountWithCertificate = menu.findItem(R.id.action_add_account_with_cert);
-
- if (Config.X509_VERIFICATION) {
- addAccount.setVisible(false);
- addAccountWithCertificate.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
- }
-
- if (!accountsLeftToEnable()) {
- enableAll.setVisible(false);
- }
- MenuItem disableAll = menu.findItem(R.id.action_disable_all);
- if (!accountsLeftToDisable()) {
- disableAll.setVisible(false);
- }
- return true;
- }
-
- @Override
- public boolean onContextItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case R.id.mgmt_account_publish_avatar:
- publishAvatar(selectedAccount);
- return true;
- case R.id.mgmt_account_disable:
- disableAccount(selectedAccount);
- return true;
- case R.id.mgmt_account_enable:
- enableAccount(selectedAccount);
- return true;
- case R.id.mgmt_account_delete:
- deleteAccount(selectedAccount);
- return true;
- case R.id.mgmt_account_announce_pgp:
- publishOpenPGPPublicKey(selectedAccount);
- return true;
- default:
- return super.onContextItemSelected(item);
- }
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- if (MenuDoubleTabUtil.shouldIgnoreTap()) {
- return false;
- }
- switch (item.getItemId()) {
- case R.id.action_add_account:
- startActivity(new Intent(this, EditAccountActivity.class));
- break;
- case R.id.action_import_backup:
- if (hasStoragePermission(REQUEST_IMPORT_BACKUP)) {
- startActivity(new Intent(this, ImportBackupActivity.class));
- }
- break;
- case R.id.action_disable_all:
- disableAllAccounts();
- break;
- case R.id.action_enable_all:
- enableAllAccounts();
- break;
- case R.id.action_add_account_with_cert:
- addAccountFromKey();
- break;
- default:
- break;
- }
- return super.onOptionsItemSelected(item);
- }
-
-
- @Override
- public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
- super.onRequestPermissionsResult(requestCode, permissions, grantResults);
- if (grantResults.length > 0) {
- if (allGranted(grantResults)) {
- switch (requestCode) {
- case REQUEST_IMPORT_BACKUP:
- startActivity(new Intent(this, ImportBackupActivity.class));
- break;
- }
- } else {
- Toast.makeText(this, R.string.no_storage_permission, Toast.LENGTH_SHORT).show();
- }
- }
- if (writeGranted(grantResults, permissions)) {
- if (xmppConnectionService != null) {
- xmppConnectionService.restartFileObserver();
- }
- }
- }
-
- @Override
- public boolean onNavigateUp() {
- if (xmppConnectionService.getConversations().size() == 0) {
- Intent contactsIntent = new Intent(this,
- StartConversationActivity.class);
- contactsIntent.setFlags(
- // if activity exists in stack, pop the stack and go back to it
- Intent.FLAG_ACTIVITY_CLEAR_TOP |
- // otherwise, make a new task for it
- Intent.FLAG_ACTIVITY_NEW_TASK |
- // don't use the new activity animation; finish
- // animation runs instead
- Intent.FLAG_ACTIVITY_NO_ANIMATION);
- startActivity(contactsIntent);
- finish();
- return true;
- } else {
- return super.onNavigateUp();
- }
- }
-
- @Override
- public void onClickTglAccountState(Account account, boolean enable) {
- if (enable) {
- enableAccount(account);
- } else {
- disableAccount(account);
- }
- }
-
- private void addAccountFromKey() {
- try {
- KeyChain.choosePrivateKeyAlias(this, this, null, null, null, -1, null);
- } catch (ActivityNotFoundException e) {
- Toast.makeText(this, R.string.device_does_not_support_certificates, Toast.LENGTH_LONG).show();
- }
- }
-
- private void publishAvatar(Account account) {
- Intent intent = new Intent(getApplicationContext(),
- PublishProfilePictureActivity.class);
- intent.putExtra(EXTRA_ACCOUNT, account.getJid().asBareJid().toEscapedString());
- startActivity(intent);
- }
-
- private void disableAllAccounts() {
- List list = new ArrayList<>();
- synchronized (this.accountList) {
- for (Account account : this.accountList) {
- if (account.isEnabled()) {
- list.add(account);
- }
- }
- }
- for (Account account : list) {
- disableAccount(account);
- }
- }
-
- private boolean accountsLeftToDisable() {
- synchronized (this.accountList) {
- for (Account account : this.accountList) {
- if (account.isEnabled()) {
- return true;
- }
- }
- return false;
- }
- }
-
- private boolean accountsLeftToEnable() {
- synchronized (this.accountList) {
- for (Account account : this.accountList) {
- if (!account.isEnabled()) {
- return true;
- }
- }
- return false;
- }
- }
-
- private void enableAllAccounts() {
- List list = new ArrayList<>();
- synchronized (this.accountList) {
- for (Account account : this.accountList) {
- if (!account.isEnabled()) {
- list.add(account);
- }
- }
- }
- for (Account account : list) {
- enableAccount(account);
- }
- }
-
- private void disableAccount(Account account) {
- account.setOption(Account.OPTION_DISABLED, true);
- if (!xmppConnectionService.updateAccount(account)) {
- Toast.makeText(this, R.string.unable_to_update_account, Toast.LENGTH_SHORT).show();
- }
- }
-
- private void enableAccount(Account account) {
- account.setOption(Account.OPTION_DISABLED, false);
- final XmppConnection connection = account.getXmppConnection();
- if (connection != null) {
- connection.resetEverything();
- }
- if (!xmppConnectionService.updateAccount(account)) {
- Toast.makeText(this, R.string.unable_to_update_account, Toast.LENGTH_SHORT).show();
- }
- }
-
- private void publishOpenPGPPublicKey(Account account) {
- if (ManageAccountActivity.this.hasPgp()) {
- announcePgp(selectedAccount, null, null, onOpenPGPKeyPublished);
- } else {
- this.showInstallPgpDialog();
- }
- }
-
- private void deleteAccount(final Account account) {
- final AlertDialog.Builder builder = new AlertDialog.Builder(this);
- builder.setTitle(getString(R.string.mgmt_account_are_you_sure));
- builder.setIconAttribute(android.R.attr.alertDialogIcon);
- builder.setMessage(getString(R.string.mgmt_account_delete_confirm_text));
- builder.setPositiveButton(getString(R.string.delete),
- (dialog, which) -> {
- xmppConnectionService.deleteAccount(account);
- selectedAccount = null;
- if (xmppConnectionService.getAccounts().size() == 0 && Config.MAGIC_CREATE_DOMAIN != null) {
- WelcomeActivity.launch(this);
- }
- });
- builder.setNegativeButton(getString(R.string.cancel), null);
- builder.create().show();
- }
-
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- super.onActivityResult(requestCode, resultCode, data);
- if (resultCode == RESULT_OK) {
- if (xmppConnectionServiceBound) {
- if (requestCode == REQUEST_CHOOSE_PGP_ID) {
- if (data.getExtras().containsKey(OpenPgpApi.EXTRA_SIGN_KEY_ID)) {
- selectedAccount.setPgpSignId(data.getExtras().getLong(OpenPgpApi.EXTRA_SIGN_KEY_ID));
- announcePgp(selectedAccount, null, null, onOpenPGPKeyPublished);
- } else {
- choosePgpSignId(selectedAccount);
- }
- } else if (requestCode == REQUEST_ANNOUNCE_PGP) {
- announcePgp(selectedAccount, null, data, onOpenPGPKeyPublished);
- }
- this.mPostponedActivityResult = null;
- } else {
- this.mPostponedActivityResult = new Pair<>(requestCode, data);
- }
- }
- }
-
- @Override
- public void alias(final String alias) {
- if (alias != null) {
- xmppConnectionService.createAccountFromKey(alias, this);
- }
- }
-
- @Override
- public void onAccountCreated(final Account account) {
- final Intent intent = new Intent(this, EditAccountActivity.class);
- intent.putExtra("jid", account.getJid().asBareJid().toString());
- intent.putExtra("init", true);
- startActivity(intent);
- }
-
- @Override
- public void informUser(final int r) {
- runOnUiThread(() -> Toast.makeText(ManageAccountActivity.this, r, Toast.LENGTH_LONG).show());
- }
-}
diff --git a/src/conversations/java/eu/siacs/conversations/ui/PickServerActivity.java b/src/conversations/java/eu/siacs/conversations/ui/PickServerActivity.java
deleted file mode 100644
index 06320d33d..000000000
--- a/src/conversations/java/eu/siacs/conversations/ui/PickServerActivity.java
+++ /dev/null
@@ -1,104 +0,0 @@
-package eu.siacs.conversations.ui;
-
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.os.Bundle;
-import android.view.MenuItem;
-
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.databinding.DataBindingUtil;
-
-import java.util.List;
-
-import eu.siacs.conversations.R;
-import eu.siacs.conversations.databinding.ActivityPickServerBinding;
-import eu.siacs.conversations.entities.Account;
-
-public class PickServerActivity extends XmppActivity {
-
- @Override
- protected void refreshUiReal() {
-
- }
-
- @Override
- void onBackendConnected() {
-
- }
-
- @Override
- public void onStart() {
- super.onStart();
- final int theme = findTheme();
- if (this.mTheme != theme) {
- recreate();
- }
- }
-
-
- @Override
- public boolean onOptionsItemSelected(final MenuItem item) {
- if (item.getItemId() == android.R.id.home) {
- startActivity(new Intent(this, WelcomeActivity.class));
- finish();
- return true;
- }
- return super.onOptionsItemSelected(item);
- }
-
- @Override
- public void onBackPressed() {
- startActivity(new Intent(this, WelcomeActivity.class));
- super.onBackPressed();
- }
-
- @Override
- public void onNewIntent(Intent intent) {
- if (intent != null) {
- setIntent(intent);
- }
- }
-
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- if (getResources().getBoolean(R.bool.portrait_only)) {
- setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
- }
- super.onCreate(savedInstanceState);
- ActivityPickServerBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_pick_server);
- setSupportActionBar(binding.toolbar);
- configureActionBar(getSupportActionBar());
- binding.useCim.setOnClickListener(v -> {
- final Intent intent = new Intent(this, MagicCreateActivity.class);
- intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
- addInviteUri(intent);
- startActivity(intent);
- });
- binding.useOwnProvider.setOnClickListener(v -> {
- List accounts = xmppConnectionService.getAccounts();
- Intent intent = new Intent(this, EditAccountActivity.class);
- intent.putExtra(EditAccountActivity.EXTRA_FORCE_REGISTER, true);
- if (accounts.size() == 1) {
- intent.putExtra("jid", accounts.get(0).getJid().asBareJid().toString());
- intent.putExtra("init", true);
- } else if (accounts.size() >= 1) {
- intent = new Intent(this, ManageAccountActivity.class);
- }
- addInviteUri(intent);
- startActivity(intent);
- });
-
- }
-
- public void addInviteUri(Intent intent) {
- StartConversationActivity.addInviteUri(intent, getIntent());
- }
-
- public static void launch(AppCompatActivity activity) {
- Intent intent = new Intent(activity, PickServerActivity.class);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
- activity.startActivity(intent);
- activity.overridePendingTransition(0, 0);
- }
-
-}
diff --git a/src/conversations/java/eu/siacs/conversations/ui/ShareViaAccountActivity.java b/src/conversations/java/eu/siacs/conversations/ui/ShareViaAccountActivity.java
deleted file mode 100644
index 762dfbb42..000000000
--- a/src/conversations/java/eu/siacs/conversations/ui/ShareViaAccountActivity.java
+++ /dev/null
@@ -1,90 +0,0 @@
-package eu.siacs.conversations.ui;
-
-import android.os.Bundle;
-import android.widget.ListView;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import eu.siacs.conversations.R;
-import eu.siacs.conversations.entities.Account;
-import eu.siacs.conversations.entities.Conversation;
-import eu.siacs.conversations.ui.adapter.AccountAdapter;
-import eu.siacs.conversations.xmpp.Jid;
-
-public class ShareViaAccountActivity extends XmppActivity {
- public static final String EXTRA_CONTACT = "contact";
- public static final String EXTRA_BODY = "body";
-
- protected final List accountList = new ArrayList<>();
- protected ListView accountListView;
- protected AccountAdapter mAccountAdapter;
-
- @Override
- protected void refreshUiReal() {
- synchronized (this.accountList) {
- accountList.clear();
- accountList.addAll(xmppConnectionService.getAccounts());
- }
- mAccountAdapter.notifyDataSetChanged();
- }
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- setContentView(R.layout.activity_manage_accounts);
- setSupportActionBar(findViewById(R.id.toolbar));
- configureActionBar(getSupportActionBar());
- accountListView = findViewById(R.id.account_list);
- this.mAccountAdapter = new AccountAdapter(this, accountList, false);
- accountListView.setAdapter(this.mAccountAdapter);
- accountListView.setOnItemClickListener((arg0, view, position, arg3) -> {
- final Account account = accountList.get(position);
- final String body = getIntent().getStringExtra(EXTRA_BODY);
-
- try {
- final Jid contact = Jid.of(getIntent().getStringExtra(EXTRA_CONTACT));
- final Conversation conversation = xmppConnectionService.findOrCreateConversation(
- account, contact, false, false);
- switchToConversation(conversation, body);
- } catch (IllegalArgumentException e) {
- // ignore error
- }
-
- finish();
- });
- }
-
- @Override
- protected void onStart() {
- super.onStart();
- final int theme = findTheme();
- if (this.mTheme != theme) {
- recreate();
- }
- }
-
- @Override
- void onBackendConnected() {
- final int numAccounts = xmppConnectionService.getAccounts().size();
-
- if (numAccounts == 1) {
- final String body = getIntent().getStringExtra(EXTRA_BODY);
- final Account account = xmppConnectionService.getAccounts().get(0);
-
- try {
- final Jid contact = Jid.of(getIntent().getStringExtra(EXTRA_CONTACT));
- final Conversation conversation = xmppConnectionService.findOrCreateConversation(
- account, contact, false, false);
- switchToConversation(conversation, body);
- } catch (IllegalArgumentException e) {
- // ignore error
- }
-
- finish();
- } else {
- refreshUiReal();
- }
- }
-}
diff --git a/src/conversations/java/eu/siacs/conversations/ui/WelcomeActivity.java b/src/conversations/java/eu/siacs/conversations/ui/WelcomeActivity.java
deleted file mode 100644
index d61c64a9c..000000000
--- a/src/conversations/java/eu/siacs/conversations/ui/WelcomeActivity.java
+++ /dev/null
@@ -1,236 +0,0 @@
-package eu.siacs.conversations.ui;
-
-import android.Manifest;
-import android.content.ActivityNotFoundException;
-import android.content.Intent;
-import android.content.pm.ActivityInfo;
-import android.net.Uri;
-import android.os.Bundle;
-import android.security.KeyChain;
-import android.security.KeyChainAliasCallback;
-import android.util.Log;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.widget.Toast;
-
-import androidx.annotation.NonNull;
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.databinding.DataBindingUtil;
-
-import java.util.Arrays;
-import java.util.List;
-
-import eu.siacs.conversations.Config;
-import eu.siacs.conversations.R;
-import eu.siacs.conversations.databinding.ActivityWelcomeBinding;
-import eu.siacs.conversations.entities.Account;
-import eu.siacs.conversations.services.XmppConnectionService;
-import eu.siacs.conversations.utils.Compatibility;
-import eu.siacs.conversations.utils.InstallReferrerUtils;
-import eu.siacs.conversations.utils.SignupUtils;
-import eu.siacs.conversations.utils.XmppUri;
-import eu.siacs.conversations.xmpp.Jid;
-
-import static eu.siacs.conversations.utils.PermissionUtils.allGranted;
-import static eu.siacs.conversations.utils.PermissionUtils.writeGranted;
-
-public class WelcomeActivity extends XmppActivity implements XmppConnectionService.OnAccountCreated, KeyChainAliasCallback {
-
- private static final int REQUEST_IMPORT_BACKUP = 0x63fb;
-
- private XmppUri inviteUri;
-
- public static void launch(AppCompatActivity activity) {
- Intent intent = new Intent(activity, WelcomeActivity.class);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
- activity.startActivity(intent);
- activity.overridePendingTransition(0, 0);
- }
-
- public void onInstallReferrerDiscovered(final Uri referrer) {
- Log.d(Config.LOGTAG, "welcome activity: on install referrer discovered " + referrer);
- if ("xmpp".equalsIgnoreCase(referrer.getScheme())) {
- final XmppUri xmppUri = new XmppUri(referrer);
- runOnUiThread(() -> processXmppUri(xmppUri));
- } else {
- Log.i(Config.LOGTAG, "install referrer was not an XMPP uri");
- }
- }
-
- private void processXmppUri(final XmppUri xmppUri) {
- if (!xmppUri.isValidJid()) {
- return;
- }
- final String preAuth = xmppUri.getParameter(XmppUri.PARAMETER_PRE_AUTH);
- final Jid jid = xmppUri.getJid();
- final Intent intent;
- if (xmppUri.isAction(XmppUri.ACTION_REGISTER)) {
- intent = SignupUtils.getTokenRegistrationIntent(this, jid, preAuth);
- } else if (xmppUri.isAction(XmppUri.ACTION_ROSTER) && "y".equals(xmppUri.getParameter(XmppUri.PARAMETER_IBR))) {
- intent = SignupUtils.getTokenRegistrationIntent(this, jid.getDomain(), preAuth);
- intent.putExtra(StartConversationActivity.EXTRA_INVITE_URI, xmppUri.toString());
- } else {
- intent = null;
- }
- if (intent != null) {
- startActivity(intent);
- finish();
- return;
- }
- this.inviteUri = xmppUri;
- }
-
- @Override
- protected void refreshUiReal() {
-
- }
-
- @Override
- void onBackendConnected() {
-
- }
-
- @Override
- public void onStart() {
- super.onStart();
- final int theme = findTheme();
- if (this.mTheme != theme) {
- recreate();
- }
- new InstallReferrerUtils(this);
- }
-
- @Override
- public void onStop() {
- super.onStop();
- }
-
- @Override
- public void onNewIntent(final Intent intent) {
- super.onNewIntent(intent);
- if (intent != null) {
- setIntent(intent);
- }
- }
-
- @Override
- protected void onCreate(final Bundle savedInstanceState) {
- if (getResources().getBoolean(R.bool.portrait_only)) {
- setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
- }
- super.onCreate(savedInstanceState);
- ActivityWelcomeBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_welcome);
- setSupportActionBar(binding.toolbar);
- configureActionBar(getSupportActionBar(), false);
- binding.registerNewAccount.setOnClickListener(v -> {
- final Intent intent = new Intent(this, PickServerActivity.class);
- addInviteUri(intent);
- startActivity(intent);
- });
- binding.useExisting.setOnClickListener(v -> {
- final List accounts = xmppConnectionService.getAccounts();
- Intent intent = new Intent(WelcomeActivity.this, EditAccountActivity.class);
- intent.putExtra(EditAccountActivity.EXTRA_FORCE_REGISTER, false);
- if (accounts.size() == 1) {
- intent.putExtra("jid", accounts.get(0).getJid().asBareJid().toString());
- intent.putExtra("init", true);
- } else if (accounts.size() >= 1) {
- intent = new Intent(WelcomeActivity.this, ManageAccountActivity.class);
- }
- addInviteUri(intent);
- startActivity(intent);
- });
-
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- getMenuInflater().inflate(R.menu.welcome_menu, menu);
- final MenuItem scan = menu.findItem(R.id.action_scan_qr_code);
- scan.setVisible(Compatibility.hasFeatureCamera(this));
- return super.onCreateOptionsMenu(menu);
- }
-
-
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case R.id.action_import_backup:
- if (hasStoragePermission(REQUEST_IMPORT_BACKUP)) {
- startActivity(new Intent(this, ImportBackupActivity.class));
- }
- break;
- case R.id.action_scan_qr_code:
- UriHandlerActivity.scan(this, true);
- break;
- case R.id.action_add_account_with_cert:
- addAccountFromKey();
- break;
- }
- return super.onOptionsItemSelected(item);
- }
-
- private void addAccountFromKey() {
- try {
- KeyChain.choosePrivateKeyAlias(this, this, null, null, null, -1, null);
- } catch (ActivityNotFoundException e) {
- Toast.makeText(this, R.string.device_does_not_support_certificates, Toast.LENGTH_LONG).show();
- }
- }
-
- @Override
- public void alias(final String alias) {
- if (alias != null) {
- xmppConnectionService.createAccountFromKey(alias, this);
- }
- }
-
- @Override
- public void onAccountCreated(final Account account) {
- final Intent intent = new Intent(this, EditAccountActivity.class);
- intent.putExtra("jid", account.getJid().asBareJid().toEscapedString());
- intent.putExtra("init", true);
- addInviteUri(intent);
- startActivity(intent);
- }
-
- @Override
- public void informUser(final int r) {
- runOnUiThread(() -> Toast.makeText(this, r, Toast.LENGTH_LONG).show());
- }
-
- @Override
- public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
- super.onRequestPermissionsResult(requestCode, permissions, grantResults);
- UriHandlerActivity.onRequestPermissionResult(this, requestCode, grantResults);
- if (grantResults.length > 0) {
- if (allGranted(grantResults)) {
- switch (requestCode) {
- case REQUEST_IMPORT_BACKUP:
- startActivity(new Intent(this, ImportBackupActivity.class));
- break;
- }
- } else if (Arrays.asList(permissions).contains(Manifest.permission.WRITE_EXTERNAL_STORAGE)) {
- Toast.makeText(this, R.string.no_storage_permission, Toast.LENGTH_SHORT).show();
- }
- }
- if (writeGranted(grantResults, permissions)) {
- if (xmppConnectionService != null) {
- xmppConnectionService.restartFileObserver();
- }
- }
- }
-
- public void addInviteUri(Intent to) {
- final Intent from = getIntent();
- if (from != null && from.hasExtra(StartConversationActivity.EXTRA_INVITE_URI)) {
- final String invite = from.getStringExtra(StartConversationActivity.EXTRA_INVITE_URI);
- to.putExtra(StartConversationActivity.EXTRA_INVITE_URI, invite);
- } else if (this.inviteUri != null) {
- Log.d(Config.LOGTAG, "injecting referrer uri into on-boarding flow");
- to.putExtra(StartConversationActivity.EXTRA_INVITE_URI, this.inviteUri.toString());
- }
- }
-
-}
diff --git a/src/conversations/java/eu/siacs/conversations/ui/adapter/BackupFileAdapter.java b/src/conversations/java/eu/siacs/conversations/ui/adapter/BackupFileAdapter.java
deleted file mode 100644
index 9857dcd8a..000000000
--- a/src/conversations/java/eu/siacs/conversations/ui/adapter/BackupFileAdapter.java
+++ /dev/null
@@ -1,170 +0,0 @@
-package eu.siacs.conversations.ui.adapter;
-
-import android.content.res.Resources;
-import android.graphics.Bitmap;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.Drawable;
-import android.os.AsyncTask;
-import android.text.format.DateUtils;
-import android.util.DisplayMetrics;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.ImageView;
-
-import androidx.annotation.NonNull;
-import androidx.databinding.DataBindingUtil;
-import androidx.recyclerview.widget.RecyclerView;
-
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.concurrent.RejectedExecutionException;
-
-import eu.siacs.conversations.R;
-import eu.siacs.conversations.databinding.AccountRowBinding;
-import eu.siacs.conversations.services.AvatarService;
-import eu.siacs.conversations.services.ImportBackupService;
-import eu.siacs.conversations.utils.BackupFileHeader;
-import eu.siacs.conversations.utils.UIHelper;
-import eu.siacs.conversations.xmpp.Jid;
-
-public class BackupFileAdapter extends RecyclerView.Adapter {
-
- private OnItemClickedListener listener;
-
- private final List files = new ArrayList<>();
-
-
- @NonNull
- @Override
- public BackupFileViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
- return new BackupFileViewHolder(DataBindingUtil.inflate(LayoutInflater.from(viewGroup.getContext()), R.layout.account_row, viewGroup, false));
- }
-
- @Override
- public void onBindViewHolder(@NonNull BackupFileViewHolder backupFileViewHolder, int position) {
- final ImportBackupService.BackupFile backupFile = files.get(position);
- final BackupFileHeader header = backupFile.getHeader();
- backupFileViewHolder.binding.accountJid.setText(header.getJid().asBareJid().toString());
- backupFileViewHolder.binding.accountStatus.setText(String.format("%s · %s",header.getApp(), DateUtils.formatDateTime(backupFileViewHolder.binding.getRoot().getContext(), header.getTimestamp(), DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR)));
- backupFileViewHolder.binding.tglAccountStatus.setVisibility(View.GONE);
- backupFileViewHolder.binding.getRoot().setOnClickListener(v -> {
- if (listener != null) {
- listener.onClick(backupFile);
- }
- });
- loadAvatar(header.getJid(), backupFileViewHolder.binding.accountImage);
- }
-
- @Override
- public int getItemCount() {
- return files.size();
- }
-
- public void setFiles(List files) {
- this.files.clear();
- this.files.addAll(files);
- notifyDataSetChanged();
- }
-
- public void setOnItemClickedListener(OnItemClickedListener listener) {
- this.listener = listener;
- }
-
- static class BackupFileViewHolder extends RecyclerView.ViewHolder {
- private final AccountRowBinding binding;
-
- BackupFileViewHolder(AccountRowBinding binding) {
- super(binding.getRoot());
- this.binding = binding;
- }
-
- }
-
- public interface OnItemClickedListener {
- void onClick(ImportBackupService.BackupFile backupFile);
- }
-
- static class BitmapWorkerTask extends AsyncTask {
- private final WeakReference imageViewReference;
- private Jid jid = null;
- private final int size;
-
- BitmapWorkerTask(ImageView imageView) {
- imageViewReference = new WeakReference<>(imageView);
- DisplayMetrics metrics = imageView.getContext().getResources().getDisplayMetrics();
- this.size = ((int) (48 * metrics.density));
- }
-
- @Override
- protected Bitmap doInBackground(Jid... params) {
- this.jid = params[0];
- return AvatarService.get(this.jid, size);
- }
-
- @Override
- protected void onPostExecute(Bitmap bitmap) {
- if (bitmap != null && !isCancelled()) {
- final ImageView imageView = imageViewReference.get();
- if (imageView != null) {
- imageView.setImageBitmap(bitmap);
- imageView.setBackgroundColor(0x00000000);
- }
- }
- }
- }
-
- private void loadAvatar(Jid jid, ImageView imageView) {
- if (cancelPotentialWork(jid, imageView)) {
- imageView.setBackgroundColor(UIHelper.getColorForName(jid.asBareJid().toString()));
- imageView.setImageDrawable(null);
- final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
- final AsyncDrawable asyncDrawable = new AsyncDrawable(imageView.getContext().getResources(), null, task);
- imageView.setImageDrawable(asyncDrawable);
- try {
- task.execute(jid);
- } catch (final RejectedExecutionException ignored) {
- }
- }
- }
-
- private static boolean cancelPotentialWork(Jid jid, ImageView imageView) {
- final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
-
- if (bitmapWorkerTask != null) {
- final Jid oldJid = bitmapWorkerTask.jid;
- if (oldJid == null || jid != oldJid) {
- bitmapWorkerTask.cancel(true);
- } else {
- return false;
- }
- }
- return true;
- }
-
- private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
- if (imageView != null) {
- final Drawable drawable = imageView.getDrawable();
- if (drawable instanceof AsyncDrawable) {
- final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
- return asyncDrawable.getBitmapWorkerTask();
- }
- }
- return null;
- }
-
- static class AsyncDrawable extends BitmapDrawable {
- private final WeakReference bitmapWorkerTaskReference;
-
- AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) {
- super(res, bitmap);
- bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask);
- }
-
- BitmapWorkerTask getBitmapWorkerTask() {
- return bitmapWorkerTaskReference.get();
- }
- }
-
-}
\ No newline at end of file
diff --git a/src/conversations/java/eu/siacs/conversations/utils/PhoneNumberUtilWrapper.java b/src/conversations/java/eu/siacs/conversations/utils/PhoneNumberUtilWrapper.java
deleted file mode 100644
index 2f7963cf6..000000000
--- a/src/conversations/java/eu/siacs/conversations/utils/PhoneNumberUtilWrapper.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package eu.siacs.conversations.utils;
-
-import android.content.Context;
-
-import eu.siacs.conversations.xmpp.Jid;
-
-public class PhoneNumberUtilWrapper {
- public static String toFormattedPhoneNumber(Context context, Jid jid) {
- throw new AssertionError("This method is not implemented in Conversations");
- }
-}
diff --git a/src/conversations/java/eu/siacs/conversations/utils/ProvisioningUtils.java b/src/conversations/java/eu/siacs/conversations/utils/ProvisioningUtils.java
deleted file mode 100644
index 593291d95..000000000
--- a/src/conversations/java/eu/siacs/conversations/utils/ProvisioningUtils.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package eu.siacs.conversations.utils;
-
-import android.app.Activity;
-import android.content.Intent;
-import android.widget.Toast;
-
-import java.util.List;
-
-import eu.siacs.conversations.R;
-import eu.siacs.conversations.entities.AccountConfiguration;
-import eu.siacs.conversations.persistance.DatabaseBackend;
-import eu.siacs.conversations.services.XmppConnectionService;
-import eu.siacs.conversations.ui.EditAccountActivity;
-import eu.siacs.conversations.xmpp.Jid;
-
-public class ProvisioningUtils {
-
- public static void provision(final Activity activity, final String json) {
- final AccountConfiguration accountConfiguration;
- try {
- accountConfiguration = AccountConfiguration.parse(json);
- } catch (final IllegalArgumentException e) {
- Toast.makeText(activity, R.string.improperly_formatted_provisioning, Toast.LENGTH_LONG).show();
- return;
- }
- final Jid jid = accountConfiguration.getJid();
- final List accounts = DatabaseBackend.getInstance(activity).getAccountJids(true);
- if (accounts.contains(jid)) {
- Toast.makeText(activity, R.string.account_already_exists, Toast.LENGTH_LONG).show();
- return;
- }
- final Intent serviceIntent = new Intent(activity, XmppConnectionService.class);
- serviceIntent.setAction(XmppConnectionService.ACTION_PROVISION_ACCOUNT);
- serviceIntent.putExtra("address", jid.asBareJid().toEscapedString());
- serviceIntent.putExtra("password", accountConfiguration.password);
- Compatibility.startService(activity, serviceIntent);
- final Intent intent = new Intent(activity, EditAccountActivity.class);
- intent.putExtra("jid", jid.asBareJid().toEscapedString());
- intent.putExtra("init", true);
- activity.startActivity(intent);
- }
-
-}
diff --git a/src/conversations/java/eu/siacs/conversations/utils/SignupUtils.java b/src/conversations/java/eu/siacs/conversations/utils/SignupUtils.java
deleted file mode 100644
index fb088234a..000000000
--- a/src/conversations/java/eu/siacs/conversations/utils/SignupUtils.java
+++ /dev/null
@@ -1,77 +0,0 @@
-package eu.siacs.conversations.utils;
-
-import android.app.Activity;
-import android.content.Intent;
-
-import eu.siacs.conversations.Config;
-import eu.siacs.conversations.entities.Account;
-import eu.siacs.conversations.services.XmppConnectionService;
-import eu.siacs.conversations.ui.ConversationsActivity;
-import eu.siacs.conversations.ui.EditAccountActivity;
-import eu.siacs.conversations.ui.MagicCreateActivity;
-import eu.siacs.conversations.ui.ManageAccountActivity;
-import eu.siacs.conversations.ui.PickServerActivity;
-import eu.siacs.conversations.ui.StartConversationActivity;
-import eu.siacs.conversations.ui.WelcomeActivity;
-import eu.siacs.conversations.xmpp.Jid;
-
-public class SignupUtils {
-
- public static boolean isSupportTokenRegistry() {
- return true;
- }
-
- public static Intent getTokenRegistrationIntent(final Activity activity, Jid jid, String preAuth) {
- final Intent intent = new Intent(activity, MagicCreateActivity.class);
- if (jid.isDomainJid()) {
- intent.putExtra(MagicCreateActivity.EXTRA_DOMAIN, jid.getDomain().toEscapedString());
- } else {
- intent.putExtra(MagicCreateActivity.EXTRA_DOMAIN, jid.getDomain().toEscapedString());
- intent.putExtra(MagicCreateActivity.EXTRA_USERNAME, jid.getEscapedLocal());
- }
- intent.putExtra(MagicCreateActivity.EXTRA_PRE_AUTH, preAuth);
- return intent;
- }
-
- public static Intent getSignUpIntent(final Activity activity) {
- return getSignUpIntent(activity, false);
- }
-
- public static Intent getSignUpIntent(final Activity activity, final boolean toServerChooser) {
- final Intent intent;
- if (toServerChooser) {
- intent = new Intent(activity, PickServerActivity.class);
- } else {
- intent = new Intent(activity, WelcomeActivity.class);
- }
- return intent;
- }
-
- public static Intent getRedirectionIntent(final ConversationsActivity activity) {
- final XmppConnectionService service = activity.xmppConnectionService;
- Account pendingAccount = AccountUtils.getPendingAccount(service);
- Intent intent;
- if (pendingAccount != null) {
- intent = new Intent(activity, EditAccountActivity.class);
- intent.putExtra("jid", pendingAccount.getJid().asBareJid().toString());
- if (!pendingAccount.isOptionSet(Account.OPTION_MAGIC_CREATE)) {
- intent.putExtra(EditAccountActivity.EXTRA_FORCE_REGISTER, pendingAccount.isOptionSet(Account.OPTION_REGISTER));
- }
- } else {
- if (service.getAccounts().size() == 0) {
- if (Config.X509_VERIFICATION) {
- intent = new Intent(activity, ManageAccountActivity.class);
- } else if (Config.MAGIC_CREATE_DOMAIN != null) {
- intent = getSignUpIntent(activity);
- } else {
- intent = new Intent(activity, EditAccountActivity.class);
- }
- } else {
- intent = new Intent(activity, StartConversationActivity.class);
- }
- }
- intent.putExtra("init", true);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
- return intent;
- }
-}
\ No newline at end of file
diff --git a/src/conversations/new_launcher-web.png b/src/conversations/new_launcher-web.png
deleted file mode 100644
index 76057f9fe..000000000
Binary files a/src/conversations/new_launcher-web.png and /dev/null differ
diff --git a/src/conversations/res/drawable-hdpi/ic_notification.png b/src/conversations/res/drawable-hdpi/ic_notification.png
deleted file mode 100644
index 32325e5f7..000000000
Binary files a/src/conversations/res/drawable-hdpi/ic_notification.png and /dev/null differ
diff --git a/src/conversations/res/drawable-hdpi/ic_unarchive_white_24dp.png b/src/conversations/res/drawable-hdpi/ic_unarchive_white_24dp.png
deleted file mode 100644
index 18730f12f..000000000
Binary files a/src/conversations/res/drawable-hdpi/ic_unarchive_white_24dp.png and /dev/null differ
diff --git a/src/conversations/res/drawable-hdpi/main_logo.png b/src/conversations/res/drawable-hdpi/main_logo.png
deleted file mode 100644
index 42d553dd7..000000000
Binary files a/src/conversations/res/drawable-hdpi/main_logo.png and /dev/null differ
diff --git a/src/conversations/res/drawable-hdpi/splash_logo.png b/src/conversations/res/drawable-hdpi/splash_logo.png
deleted file mode 100644
index d8efc71af..000000000
Binary files a/src/conversations/res/drawable-hdpi/splash_logo.png and /dev/null differ
diff --git a/src/conversations/res/drawable-mdpi/ic_notification.png b/src/conversations/res/drawable-mdpi/ic_notification.png
deleted file mode 100644
index 379720e94..000000000
Binary files a/src/conversations/res/drawable-mdpi/ic_notification.png and /dev/null differ
diff --git a/src/conversations/res/drawable-mdpi/ic_unarchive_white_24dp.png b/src/conversations/res/drawable-mdpi/ic_unarchive_white_24dp.png
deleted file mode 100644
index 8ec62cd34..000000000
Binary files a/src/conversations/res/drawable-mdpi/ic_unarchive_white_24dp.png and /dev/null differ
diff --git a/src/conversations/res/drawable-mdpi/main_logo.png b/src/conversations/res/drawable-mdpi/main_logo.png
deleted file mode 100644
index d81b73638..000000000
Binary files a/src/conversations/res/drawable-mdpi/main_logo.png and /dev/null differ
diff --git a/src/conversations/res/drawable-mdpi/splash_logo.png b/src/conversations/res/drawable-mdpi/splash_logo.png
deleted file mode 100644
index 1b10d1f91..000000000
Binary files a/src/conversations/res/drawable-mdpi/splash_logo.png and /dev/null differ
diff --git a/src/conversations/res/drawable-xhdpi/ic_notification.png b/src/conversations/res/drawable-xhdpi/ic_notification.png
deleted file mode 100644
index e14da5dc6..000000000
Binary files a/src/conversations/res/drawable-xhdpi/ic_notification.png and /dev/null differ
diff --git a/src/conversations/res/drawable-xhdpi/ic_unarchive_white_24dp.png b/src/conversations/res/drawable-xhdpi/ic_unarchive_white_24dp.png
deleted file mode 100644
index a0a1509a1..000000000
Binary files a/src/conversations/res/drawable-xhdpi/ic_unarchive_white_24dp.png and /dev/null differ
diff --git a/src/conversations/res/drawable-xhdpi/main_logo.png b/src/conversations/res/drawable-xhdpi/main_logo.png
deleted file mode 100644
index 19fbe70b8..000000000
Binary files a/src/conversations/res/drawable-xhdpi/main_logo.png and /dev/null differ
diff --git a/src/conversations/res/drawable-xhdpi/splash_logo.png b/src/conversations/res/drawable-xhdpi/splash_logo.png
deleted file mode 100644
index 9458b791c..000000000
Binary files a/src/conversations/res/drawable-xhdpi/splash_logo.png and /dev/null differ
diff --git a/src/conversations/res/drawable-xxhdpi/ic_notification.png b/src/conversations/res/drawable-xxhdpi/ic_notification.png
deleted file mode 100644
index 6adbc8f64..000000000
Binary files a/src/conversations/res/drawable-xxhdpi/ic_notification.png and /dev/null differ
diff --git a/src/conversations/res/drawable-xxhdpi/ic_unarchive_white_24dp.png b/src/conversations/res/drawable-xxhdpi/ic_unarchive_white_24dp.png
deleted file mode 100644
index 20d015751..000000000
Binary files a/src/conversations/res/drawable-xxhdpi/ic_unarchive_white_24dp.png and /dev/null differ
diff --git a/src/conversations/res/drawable-xxhdpi/main_logo.png b/src/conversations/res/drawable-xxhdpi/main_logo.png
deleted file mode 100644
index cc091c437..000000000
Binary files a/src/conversations/res/drawable-xxhdpi/main_logo.png and /dev/null differ
diff --git a/src/conversations/res/drawable-xxhdpi/splash_logo.png b/src/conversations/res/drawable-xxhdpi/splash_logo.png
deleted file mode 100644
index 83c2abe6a..000000000
Binary files a/src/conversations/res/drawable-xxhdpi/splash_logo.png and /dev/null differ
diff --git a/src/conversations/res/drawable-xxxhdpi/ic_notification.png b/src/conversations/res/drawable-xxxhdpi/ic_notification.png
deleted file mode 100644
index 65d106b8c..000000000
Binary files a/src/conversations/res/drawable-xxxhdpi/ic_notification.png and /dev/null differ
diff --git a/src/conversations/res/drawable-xxxhdpi/ic_unarchive_white_24dp.png b/src/conversations/res/drawable-xxxhdpi/ic_unarchive_white_24dp.png
deleted file mode 100644
index a789520ba..000000000
Binary files a/src/conversations/res/drawable-xxxhdpi/ic_unarchive_white_24dp.png and /dev/null differ
diff --git a/src/conversations/res/drawable-xxxhdpi/main_logo.png b/src/conversations/res/drawable-xxxhdpi/main_logo.png
deleted file mode 100644
index 3e6ddd877..000000000
Binary files a/src/conversations/res/drawable-xxxhdpi/main_logo.png and /dev/null differ
diff --git a/src/conversations/res/drawable-xxxhdpi/splash_logo.png b/src/conversations/res/drawable-xxxhdpi/splash_logo.png
deleted file mode 100644
index 349070ba2..000000000
Binary files a/src/conversations/res/drawable-xxxhdpi/splash_logo.png and /dev/null differ
diff --git a/src/conversations/res/layout/activity_easy_invite.xml b/src/conversations/res/layout/activity_easy_invite.xml
deleted file mode 100644
index 8bbf11c03..000000000
--- a/src/conversations/res/layout/activity_easy_invite.xml
+++ /dev/null
@@ -1,83 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/conversations/res/layout/activity_import_backup.xml b/src/conversations/res/layout/activity_import_backup.xml
deleted file mode 100644
index 5435c0f72..000000000
--- a/src/conversations/res/layout/activity_import_backup.xml
+++ /dev/null
@@ -1,45 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/conversations/res/layout/activity_pick_server.xml b/src/conversations/res/layout/activity_pick_server.xml
deleted file mode 100644
index d55ea78cc..000000000
--- a/src/conversations/res/layout/activity_pick_server.xml
+++ /dev/null
@@ -1,90 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/conversations/res/layout/activity_welcome.xml b/src/conversations/res/layout/activity_welcome.xml
deleted file mode 100644
index 33076858a..000000000
--- a/src/conversations/res/layout/activity_welcome.xml
+++ /dev/null
@@ -1,91 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/conversations/res/layout/dialog_enter_password.xml b/src/conversations/res/layout/dialog_enter_password.xml
deleted file mode 100644
index 40f3ba34d..000000000
--- a/src/conversations/res/layout/dialog_enter_password.xml
+++ /dev/null
@@ -1,47 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/conversations/res/layout/magic_create.xml b/src/conversations/res/layout/magic_create.xml
deleted file mode 100644
index f6e0436a5..000000000
--- a/src/conversations/res/layout/magic_create.xml
+++ /dev/null
@@ -1,101 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/conversations/res/menu/easy_onboarding_invite.xml b/src/conversations/res/menu/easy_onboarding_invite.xml
deleted file mode 100644
index 0e086b515..000000000
--- a/src/conversations/res/menu/easy_onboarding_invite.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
\ No newline at end of file
diff --git a/src/conversations/res/menu/manageaccounts.xml b/src/conversations/res/menu/manageaccounts.xml
deleted file mode 100644
index 5a26beaf9..000000000
--- a/src/conversations/res/menu/manageaccounts.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-
-
\ No newline at end of file
diff --git a/src/conversations/res/menu/welcome_menu.xml b/src/conversations/res/menu/welcome_menu.xml
deleted file mode 100644
index f07a2b91e..000000000
--- a/src/conversations/res/menu/welcome_menu.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-
\ No newline at end of file
diff --git a/src/conversationsFree/java/eu/siacs/conversations/utils/InstallReferrerUtils.java b/src/conversationsFree/java/eu/siacs/conversations/utils/InstallReferrerUtils.java
deleted file mode 100644
index 3aa49798d..000000000
--- a/src/conversationsFree/java/eu/siacs/conversations/utils/InstallReferrerUtils.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package eu.siacs.conversations.utils;
-
-import eu.siacs.conversations.ui.MagicCreateActivity;
-import eu.siacs.conversations.ui.WelcomeActivity;
-
-public class InstallReferrerUtils {
-
- public InstallReferrerUtils(WelcomeActivity welcomeActivity) {
-
- }
-
- public static void markInstallReferrerExecuted(MagicCreateActivity magicCreateActivity) {
- //stub
- }
-}
\ No newline at end of file
diff --git a/src/conversationsPlaystore/java/eu/siacs/conversations/utils/InstallReferrerUtils.java b/src/conversationsPlaystore/java/eu/siacs/conversations/utils/InstallReferrerUtils.java
deleted file mode 100644
index 360011bea..000000000
--- a/src/conversationsPlaystore/java/eu/siacs/conversations/utils/InstallReferrerUtils.java
+++ /dev/null
@@ -1,70 +0,0 @@
-package eu.siacs.conversations.utils;
-
-import android.app.Activity;
-import android.content.SharedPreferences;
-import android.net.Uri;
-import android.os.RemoteException;
-import android.preference.PreferenceManager;
-import android.util.Log;
-
-import com.android.installreferrer.api.InstallReferrerClient;
-import com.android.installreferrer.api.InstallReferrerStateListener;
-import com.android.installreferrer.api.ReferrerDetails;
-import com.google.common.base.Strings;
-
-import eu.siacs.conversations.Config;
-import eu.siacs.conversations.ui.WelcomeActivity;
-
-public class InstallReferrerUtils implements InstallReferrerStateListener {
-
- private static final String PROCESSED_INSTALL_REFERRER = "processed_install_referrer";
-
-
- private final WelcomeActivity welcomeActivity;
- private final InstallReferrerClient installReferrerClient;
-
-
- public InstallReferrerUtils(WelcomeActivity welcomeActivity) {
- this.welcomeActivity = welcomeActivity;
- final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(welcomeActivity);
- if (preferences.getBoolean(PROCESSED_INSTALL_REFERRER, false)) {
- Log.d(Config.LOGTAG, "install referrer already processed");
- this.installReferrerClient = null;
- return;
- }
- this.installReferrerClient = InstallReferrerClient.newBuilder(welcomeActivity).build();
- try {
- this.installReferrerClient.startConnection(this);
- } catch (SecurityException e) {
- Log.e(Config.LOGTAG, "unable to start connection to InstallReferrerClient", e);
- }
- }
-
- public static void markInstallReferrerExecuted(final Activity context) {
- final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
- preferences.edit().putBoolean(PROCESSED_INSTALL_REFERRER, true).apply();
- }
-
- @Override
- public void onInstallReferrerSetupFinished(int responseCode) {
- if (responseCode == InstallReferrerClient.InstallReferrerResponse.OK) {
- try {
- final ReferrerDetails referrerDetails = installReferrerClient.getInstallReferrer();
- final String referrer = referrerDetails.getInstallReferrer();
- if (Strings.isNullOrEmpty(referrer)) {
- return;
- }
- welcomeActivity.onInstallReferrerDiscovered(Uri.parse(referrer));
- } catch (final RemoteException | IllegalArgumentException e) {
- Log.d(Config.LOGTAG, "unable to get install referrer", e);
- }
- } else {
- Log.d(Config.LOGTAG, "unable to setup install referrer client. code=" + responseCode);
- }
- }
-
- @Override
- public void onInstallReferrerServiceDisconnected() {
-
- }
-}
\ No newline at end of file
diff --git a/src/free/java/eu/siacs/conversations/services/EmojiInitializationService.java b/src/free/java/eu/siacs/conversations/services/EmojiInitializationService.java
deleted file mode 100644
index 2618d3809..000000000
--- a/src/free/java/eu/siacs/conversations/services/EmojiInitializationService.java
+++ /dev/null
@@ -1,14 +0,0 @@
-package eu.siacs.conversations.services;
-
-import android.content.Context;
-
-import androidx.emoji2.bundled.BundledEmojiCompatConfig;
-import androidx.emoji2.text.EmojiCompat;
-
-public class EmojiInitializationService {
-
- public static void execute(final Context context) {
- EmojiCompat.init(new BundledEmojiCompatConfig(context).setReplaceAll(true));
- }
-
-}
diff --git a/src/free/java/eu/siacs/conversations/services/PushManagementService.java b/src/free/java/eu/siacs/conversations/services/PushManagementService.java
deleted file mode 100644
index f436da434..000000000
--- a/src/free/java/eu/siacs/conversations/services/PushManagementService.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package eu.siacs.conversations.services;
-
-import eu.siacs.conversations.entities.Account;
-import eu.siacs.conversations.entities.Conversation;
-
-public class PushManagementService {
-
- protected final XmppConnectionService mXmppConnectionService;
-
- public PushManagementService(XmppConnectionService service) {
- this.mXmppConnectionService = service;
- }
-
- void registerPushTokenOnServer(Account account) {
- //stub implementation. only affects playstore flavor
- }
-
- void unregisterChannel(Account account, String hash) {
- //stub implementation. only affects playstore flavor
- }
-
- public boolean available(Account account) {
- return false;
- }
-
- public boolean isStub() {
- return true;
- }
-
- public boolean availableAndUseful(Account account) {
- return false;
- }
-}
diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml
deleted file mode 100644
index f3ea49578..000000000
--- a/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,361 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/main/java/eu/siacs/conversations/Config.java b/src/main/java/eu/siacs/conversations/Config.java
deleted file mode 100644
index 812f6ae10..000000000
--- a/src/main/java/eu/siacs/conversations/Config.java
+++ /dev/null
@@ -1,214 +0,0 @@
-package eu.siacs.conversations;
-
-import android.graphics.Bitmap;
-import android.net.Uri;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-import java.util.Locale;
-
-import eu.siacs.conversations.crypto.XmppDomainVerifier;
-import eu.siacs.conversations.xmpp.Jid;
-import eu.siacs.conversations.xmpp.chatstate.ChatState;
-
-public final class Config {
- private static final int UNENCRYPTED = 1;
- private static final int OPENPGP = 2;
- private static final int OMEMO = 8;
-
- private static final int ENCRYPTION_MASK = UNENCRYPTED | OPENPGP | OMEMO;
-
- public static boolean supportUnencrypted() {
- return (ENCRYPTION_MASK & UNENCRYPTED) != 0;
- }
-
- public static boolean supportOpenPgp() {
- return (ENCRYPTION_MASK & OPENPGP) != 0;
- }
-
- public static boolean supportOmemo() {
- return (ENCRYPTION_MASK & OMEMO) != 0;
- }
-
- public static boolean omemoOnly() {
- return !multipleEncryptionChoices() && supportOmemo();
- }
-
- public static boolean multipleEncryptionChoices() {
- return (ENCRYPTION_MASK & (ENCRYPTION_MASK - 1)) != 0;
- }
-
- public static final String LOGTAG = BuildConfig.APP_NAME.toLowerCase(Locale.US);
-
- public static final Jid BUG_REPORTS = Jid.of("bugs@conversations.im");
- public static final Uri HELP = Uri.parse("https://help.conversations.im");
-
-
- public static final String DOMAIN_LOCK = null; //only allow account creation for this domain
- public static final String MAGIC_CREATE_DOMAIN = "conversations.im";
- public static final Jid QUICKSY_DOMAIN = Jid.of("quicksy.im");
-
- public static final String CHANNEL_DISCOVERY = "https://search.jabber.network";
-
- public static final boolean DISALLOW_REGISTRATION_IN_UI = false; //hide the register checkbox
-
- public static final boolean USE_RANDOM_RESOURCE_ON_EVERY_BIND = false;
-
- public static final boolean ALLOW_NON_TLS_CONNECTIONS = false; //very dangerous. you should have a good reason to set this to true
-
- public static final long CONTACT_SYNC_RETRY_INTERVAL = 1000L * 60 * 5;
-
-
- public static final boolean QUICKSTART_ENABLED = true;
-
- //Notification settings
- public static final boolean HIDE_MESSAGE_TEXT_IN_NOTIFICATION = false;
- public static final boolean ALWAYS_NOTIFY_BY_DEFAULT = false;
- public static final boolean SUPPRESS_ERROR_NOTIFICATION = false;
-
-
- public static final boolean DISABLE_BAN = false; // disables the ability to ban users from rooms
-
- public static final int PING_MAX_INTERVAL = 300;
- public static final int IDLE_PING_INTERVAL = 600; //540 is minimum according to docs;
- public static final int PING_MIN_INTERVAL = 30;
- public static final int LOW_PING_TIMEOUT = 1; // used after push received
- public static final int PING_TIMEOUT = 15;
- public static final int SOCKET_TIMEOUT = 15;
- public static final int CONNECT_TIMEOUT = 90;
- public static final int POST_CONNECTIVITY_CHANGE_PING_INTERVAL = 30;
- public static final int CONNECT_DISCO_TIMEOUT = 20;
- public static final int MINI_GRACE_PERIOD = 750;
-
- public static final boolean XEP_0392 = true; //enables XEP-0392 v0.6.0
-
- public static final int AVATAR_SIZE = 192;
- public static final Bitmap.CompressFormat AVATAR_FORMAT = Bitmap.CompressFormat.JPEG;
- public static final int AVATAR_CHAR_LIMIT = 9400;
-
- public static final int IMAGE_SIZE = 1920;
- public static final Bitmap.CompressFormat IMAGE_FORMAT = Bitmap.CompressFormat.JPEG;
- public static final int IMAGE_QUALITY = 75;
-
- public static final int MESSAGE_MERGE_WINDOW = 20;
-
- public static final int PAGE_SIZE = 50;
- public static final int MAX_NUM_PAGES = 3;
- public static final int MAX_SEARCH_RESULTS = 300;
-
- public static final int REFRESH_UI_INTERVAL = 500;
-
- public static final int MAX_DISPLAY_MESSAGE_CHARS = 4096;
- public static final int MAX_STORAGE_MESSAGE_CHARS = 2 * 1024 * 1024; //2MB
-
- public static final long MILLISECONDS_IN_DAY = 24 * 60 * 60 * 1000;
-
- //remove *other* omemo devices from *your* device list announcement after not seeing any activity from them for 42 days. They will automatically add themselves after coming back online.
- public static final long OMEMO_AUTO_EXPIRY = 42 * MILLISECONDS_IN_DAY;
-
- public static final boolean REMOVE_BROKEN_DEVICES = false;
- public static final boolean OMEMO_PADDING = false;
- public static final boolean PUT_AUTH_TAG_INTO_KEY = true;
- public static final boolean AUTOMATICALLY_COMPLETE_SESSIONS = true;
-
- public static final boolean USE_BOOKMARKS2 = false;
-
- public static final boolean DISABLE_PROXY_LOOKUP = false; //useful to debug ibb
- public static final boolean USE_DIRECT_JINGLE_CANDIDATES = true;
- public static final boolean DISABLE_HTTP_UPLOAD = false;
- public static final boolean EXTENDED_SM_LOGGING = false; // log stanza counts
- public static final boolean BACKGROUND_STANZA_LOGGING = false; //log all stanzas that were received while the app is in background
- public static final boolean RESET_ATTEMPT_COUNT_ON_NETWORK_CHANGE = true; //setting to true might increase power consumption
-
- public static final boolean ENCRYPT_ON_HTTP_UPLOADED = false;
-
- public static final boolean X509_VERIFICATION = false; //use x509 certificates to verify OMEMO keys
- public static final boolean REQUIRE_RTP_VERIFICATION = false; //require a/v calls to be verified with OMEMO
-
- public static final boolean ONLY_INTERNAL_STORAGE = false; //use internal storage instead of sdcard to save attachments
-
- public static final boolean IGNORE_ID_REWRITE_IN_MUC = true;
- public static final boolean MUC_LEAVE_BEFORE_JOIN = false;
-
- public static final boolean USE_LMC_VERSION_1_1 = true;
-
- public static final long MAM_MAX_CATCHUP = MILLISECONDS_IN_DAY * 5;
- public static final int MAM_MAX_MESSAGES = 750;
-
- public static final ChatState DEFAULT_CHAT_STATE = ChatState.ACTIVE;
- public static final int TYPING_TIMEOUT = 8;
-
- public static final int EXPIRY_INTERVAL = 30 * 60 * 1000; // 30 minutes
-
- public static final String[] ENABLED_CIPHERS = {
- "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256",
- "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA384",
- "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA256",
- "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384",
- "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
- "TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
-
- "TLS_DHE_RSA_WITH_AES_128_GCM_SHA256",
- "TLS_DHE_RSA_WITH_AES_128_GCM_SHA384",
- "TLS_DHE_RSA_WITH_AES_256_GCM_SHA256",
- "TLS_DHE_RSA_WITH_AES_256_GCM_SHA384",
-
- "TLS_DHE_RSA_WITH_CAMELLIA_256_SHA",
-
- // Fallback.
- "TLS_RSA_WITH_AES_128_GCM_SHA256",
- "TLS_RSA_WITH_AES_128_GCM_SHA384",
- "TLS_RSA_WITH_AES_256_GCM_SHA256",
- "TLS_RSA_WITH_AES_256_GCM_SHA384",
- "TLS_RSA_WITH_AES_128_CBC_SHA256",
- "TLS_RSA_WITH_AES_128_CBC_SHA384",
- "TLS_RSA_WITH_AES_256_CBC_SHA256",
- "TLS_RSA_WITH_AES_256_CBC_SHA384",
- "TLS_RSA_WITH_AES_128_CBC_SHA",
- "TLS_RSA_WITH_AES_256_CBC_SHA",
- };
-
- public static final String[] WEAK_CIPHER_PATTERNS = {
- "_NULL_",
- "_EXPORT_",
- "_anon_",
- "_RC4_",
- "_DES_",
- "_MD5",
- };
-
- public static class OMEMO_EXCEPTIONS {
- //if the own account matches one of the following domains OMEMO won’t be turned on automatically
- public static final List ACCOUNT_DOMAINS = Collections.singletonList("s.ms");
-
- //if the contacts domain matches one of the following domains OMEMO won’t be turned on automatically
- //can be used for well known, widely used gateways
- private static final List CONTACT_DOMAINS = Arrays.asList(
- "cheogram.com",
- "*.covid.monal.im"
- );
-
- public static boolean matchesContactDomain(final String domain) {
- return XmppDomainVerifier.matchDomain(domain, CONTACT_DOMAINS);
- }
- }
-
- private Config() {
- }
-
- public static final class Map {
- public final static double INITIAL_ZOOM_LEVEL = 4;
- public final static double FINAL_ZOOM_LEVEL = 15;
- public final static int MY_LOCATION_INDICATOR_SIZE = 10;
- public final static int MY_LOCATION_INDICATOR_OUTLINE_SIZE = 3;
- public final static long LOCATION_FIX_TIME_DELTA = 1000 * 10; // ms
- public final static float LOCATION_FIX_SPACE_DELTA = 10; // m
- public final static int LOCATION_FIX_SIGNIFICANT_TIME_DELTA = 1000 * 60 * 2; // ms
- }
-
- // How deep nested quotes should be displayed. '2' means one quote nested in another.
- public static final int QUOTE_MAX_DEPTH = 7;
- // How deep nested quotes should be created on quoting a message.
- public static final int QUOTING_MAX_DEPTH = 2;
-}
diff --git a/src/main/java/eu/siacs/conversations/android/AbstractPhoneContact.java b/src/main/java/eu/siacs/conversations/android/AbstractPhoneContact.java
deleted file mode 100644
index 93b7f1a1e..000000000
--- a/src/main/java/eu/siacs/conversations/android/AbstractPhoneContact.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package eu.siacs.conversations.android;
-
-import android.database.Cursor;
-import android.net.Uri;
-import android.provider.ContactsContract;
-import android.text.TextUtils;
-
-public abstract class AbstractPhoneContact {
-
- private final Uri lookupUri;
- private final String displayName;
- private final String photoUri;
-
-
- AbstractPhoneContact(Cursor cursor) {
- int phoneId = cursor.getInt(cursor.getColumnIndex(ContactsContract.Data._ID));
- String lookupKey = cursor.getString(cursor.getColumnIndex(ContactsContract.Data.LOOKUP_KEY));
- this.lookupUri = ContactsContract.Contacts.getLookupUri(phoneId, lookupKey);
- this.displayName = cursor.getString(cursor.getColumnIndex(ContactsContract.Data.DISPLAY_NAME));
- this.photoUri = cursor.getString(cursor.getColumnIndex(ContactsContract.Data.PHOTO_URI));
- }
-
- public Uri getLookupUri() {
- return lookupUri;
- }
-
- public String getDisplayName() {
- return displayName;
- }
-
- public String getPhotoUri() {
- return photoUri;
- }
-
-
- public int rating() {
- return (TextUtils.isEmpty(displayName) ? 0 : 2) + (TextUtils.isEmpty(photoUri) ? 0 : 1);
- }
-}
diff --git a/src/main/java/eu/siacs/conversations/android/JabberIdContact.java b/src/main/java/eu/siacs/conversations/android/JabberIdContact.java
deleted file mode 100644
index 0b701d27a..000000000
--- a/src/main/java/eu/siacs/conversations/android/JabberIdContact.java
+++ /dev/null
@@ -1,76 +0,0 @@
-package eu.siacs.conversations.android;
-
-import android.Manifest;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.database.Cursor;
-import android.os.Build;
-import android.provider.ContactsContract;
-import android.util.Log;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Map;
-
-import eu.siacs.conversations.Config;
-import eu.siacs.conversations.xmpp.Jid;
-
-public class JabberIdContact extends AbstractPhoneContact {
-
- private static final String[] PROJECTION = new String[]{ContactsContract.Data._ID,
- ContactsContract.Data.DISPLAY_NAME,
- ContactsContract.Data.PHOTO_URI,
- ContactsContract.Data.LOOKUP_KEY,
- ContactsContract.CommonDataKinds.Im.DATA
- };
- private static final String SELECTION = ContactsContract.Data.MIMETYPE + "=? AND (" + ContactsContract.CommonDataKinds.Im.PROTOCOL + "=? or (" + ContactsContract.CommonDataKinds.Im.PROTOCOL + "=? and lower(" + ContactsContract.CommonDataKinds.Im.CUSTOM_PROTOCOL + ")=?))";
-
- private static final String[] SELECTION_ARGS = {
- ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE,
- String.valueOf(ContactsContract.CommonDataKinds.Im.PROTOCOL_JABBER),
- String.valueOf(ContactsContract.CommonDataKinds.Im.PROTOCOL_CUSTOM),
- "xmpp"
- };
-
- private final Jid jid;
-
- private JabberIdContact(Cursor cursor) throws IllegalArgumentException {
- super(cursor);
- try {
- this.jid = Jid.of(cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Im.DATA)));
- } catch (IllegalArgumentException | NullPointerException e) {
- throw new IllegalArgumentException(e);
- }
- }
-
- public Jid getJid() {
- return jid;
- }
-
- public static Map load(Context context) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && context.checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
- return Collections.emptyMap();
- }
- try (final Cursor cursor = context.getContentResolver().query(ContactsContract.Data.CONTENT_URI, PROJECTION, SELECTION, SELECTION_ARGS, null)) {
- if (cursor == null) {
- return Collections.emptyMap();
- }
- final HashMap contacts = new HashMap<>();
- while (cursor.moveToNext()) {
- try {
- final JabberIdContact contact = new JabberIdContact(cursor);
- final JabberIdContact preexisting = contacts.put(contact.getJid(), contact);
- if (preexisting == null || preexisting.rating() < contact.rating()) {
- contacts.put(contact.getJid(), contact);
- }
- } catch (final IllegalArgumentException e) {
- Log.d(Config.LOGTAG, "unable to create jabber id contact");
- }
- }
- return contacts;
- } catch (final Exception e) {
- Log.d(Config.LOGTAG, "unable to query", e);
- return Collections.emptyMap();
- }
- }
-}
diff --git a/src/main/java/eu/siacs/conversations/crypto/DomainHostnameVerifier.java b/src/main/java/eu/siacs/conversations/crypto/DomainHostnameVerifier.java
deleted file mode 100644
index 4349db45e..000000000
--- a/src/main/java/eu/siacs/conversations/crypto/DomainHostnameVerifier.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package eu.siacs.conversations.crypto;
-
-import javax.net.ssl.HostnameVerifier;
-import javax.net.ssl.SSLPeerUnverifiedException;
-import javax.net.ssl.SSLSession;
-
-public interface DomainHostnameVerifier extends HostnameVerifier {
-
- boolean verify(String domain, String hostname, SSLSession sslSession) throws SSLPeerUnverifiedException;
-
-}
diff --git a/src/main/java/eu/siacs/conversations/crypto/OmemoSetting.java b/src/main/java/eu/siacs/conversations/crypto/OmemoSetting.java
deleted file mode 100644
index a531c39f3..000000000
--- a/src/main/java/eu/siacs/conversations/crypto/OmemoSetting.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Copyright (c) 2018, Daniel Gultsch All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without modification,
- * are permitted provided that the following conditions are met:
- *
- * 1. Redistributions of source code must retain the above copyright notice, this
- * list of conditions and the following disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following disclaimer in the documentation and/or
- * other materials provided with the distribution.
- *
- * 3. Neither the name of the copyright holder nor the names of its contributors
- * may be used to endorse or promote products derived from this software without
- * specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
- * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
- * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package eu.siacs.conversations.crypto;
-
-
-import android.content.Context;
-import android.content.SharedPreferences;
-import android.preference.PreferenceManager;
-
-import com.google.common.base.Strings;
-
-import eu.siacs.conversations.Config;
-import eu.siacs.conversations.R;
-import eu.siacs.conversations.entities.Message;
-import eu.siacs.conversations.ui.SettingsActivity;
-
-public class OmemoSetting {
-
- private static boolean always = false;
- private static int encryption = Message.ENCRYPTION_AXOLOTL;
-
- public static boolean isAlways() {
- return always;
- }
-
- public static int getEncryption() {
- return encryption;
- }
-
- public static void load(final Context context, final SharedPreferences sharedPreferences) {
- if (Config.omemoOnly()) {
- always = true;
- encryption = Message.ENCRYPTION_AXOLOTL;
- return;
- }
- final String value = sharedPreferences.getString(SettingsActivity.OMEMO_SETTING, context.getResources().getString(R.string.omemo_setting_default));
- switch (Strings.nullToEmpty(value)) {
- case "always":
- always = true;
- encryption = Message.ENCRYPTION_AXOLOTL;
- break;
- case "default_off":
- always = false;
- encryption = Message.ENCRYPTION_NONE;
- break;
- default:
- always = false;
- encryption = Message.ENCRYPTION_AXOLOTL;
- break;
-
- }
- }
-
- public static void load(final Context context) {
- load(context, PreferenceManager.getDefaultSharedPreferences(context));
- }
-}
diff --git a/src/main/java/eu/siacs/conversations/crypto/PgpDecryptionService.java b/src/main/java/eu/siacs/conversations/crypto/PgpDecryptionService.java
deleted file mode 100644
index db84e0cf4..000000000
--- a/src/main/java/eu/siacs/conversations/crypto/PgpDecryptionService.java
+++ /dev/null
@@ -1,274 +0,0 @@
-package eu.siacs.conversations.crypto;
-
-import android.app.PendingIntent;
-import android.content.Intent;
-import android.util.Log;
-
-import org.openintents.openpgp.OpenPgpMetadata;
-import org.openintents.openpgp.util.OpenPgpApi;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.ArrayDeque;
-import java.util.HashSet;
-import java.util.List;
-
-import eu.siacs.conversations.Config;
-import eu.siacs.conversations.entities.Conversation;
-import eu.siacs.conversations.entities.DownloadableFile;
-import eu.siacs.conversations.entities.Message;
-import eu.siacs.conversations.http.HttpConnectionManager;
-import eu.siacs.conversations.services.XmppConnectionService;
-import eu.siacs.conversations.utils.MimeUtils;
-
-public class PgpDecryptionService {
-
- protected final ArrayDeque messages = new ArrayDeque<>();
- protected final HashSet pendingNotifications = new HashSet<>();
- private final XmppConnectionService mXmppConnectionService;
- private OpenPgpApi openPgpApi = null;
- private Message currentMessage;
- private PendingIntent pendingIntent;
- private Intent userInteractionResult;
-
-
- public PgpDecryptionService(XmppConnectionService service) {
- this.mXmppConnectionService = service;
- }
-
- public synchronized boolean decrypt(final Message message, boolean notify) {
- messages.add(message);
- if (notify && pendingIntent == null) {
- pendingNotifications.add(message);
- continueDecryption();
- return false;
- } else {
- continueDecryption();
- return notify;
- }
- }
-
- public synchronized void decrypt(final List list) {
- for (Message message : list) {
- if (message.getEncryption() == Message.ENCRYPTION_PGP) {
- messages.add(message);
- }
- }
- continueDecryption();
- }
-
- public synchronized void discard(List discards) {
- this.messages.removeAll(discards);
- this.pendingNotifications.removeAll(discards);
- }
-
- public synchronized void discard(Message message) {
- this.messages.remove(message);
- this.pendingNotifications.remove(message);
- }
-
- public void giveUpCurrentDecryption() {
- Message message;
- synchronized (this) {
- if (currentMessage != null) {
- return;
- }
- message = messages.peekFirst();
- if (message == null) {
- return;
- }
- discard(message);
- }
- synchronized (message) {
- if (message.getEncryption() == Message.ENCRYPTION_PGP) {
- message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED);
- }
- }
- mXmppConnectionService.updateMessage(message, false);
- continueDecryption(true);
- }
-
- protected synchronized void decryptNext() {
- if (pendingIntent == null
- && getOpenPgpApi() != null
- && (currentMessage = messages.poll()) != null) {
- new Thread(new Runnable() {
- @Override
- public void run() {
- executeApi(currentMessage);
- decryptNext();
- }
- }).start();
- }
- }
-
- public synchronized void continueDecryption(boolean resetPending) {
- if (resetPending) {
- this.pendingIntent = null;
- }
- continueDecryption();
- }
-
- public synchronized void continueDecryption(Intent userInteractionResult) {
- this.pendingIntent = null;
- this.userInteractionResult = userInteractionResult;
- continueDecryption();
- }
-
- public synchronized void continueDecryption() {
- if (currentMessage == null) {
- decryptNext();
- }
- }
-
- private synchronized OpenPgpApi getOpenPgpApi() {
- if (openPgpApi == null) {
- this.openPgpApi = mXmppConnectionService.getOpenPgpApi();
- }
- return this.openPgpApi;
- }
-
- private void executeApi(Message message) {
- boolean skipNotificationPush = false;
- synchronized (message) {
- Intent params = userInteractionResult != null ? userInteractionResult : new Intent();
- params.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
- if (message.getType() == Message.TYPE_TEXT) {
- InputStream is = new ByteArrayInputStream(message.getBody().getBytes());
- final OutputStream os = new ByteArrayOutputStream();
- Intent result = getOpenPgpApi().executeApi(params, is, os);
- switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) {
- case OpenPgpApi.RESULT_CODE_SUCCESS:
- try {
- os.flush();
- final String body = os.toString();
- message.setBody(body);
- message.setEncryption(Message.ENCRYPTION_DECRYPTED);
- final HttpConnectionManager manager = mXmppConnectionService.getHttpConnectionManager();
- if (message.trusted()
- && message.treatAsDownloadable()
- && manager.getAutoAcceptFileSize() > 0) {
- manager.createNewDownloadConnection(message);
- }
- } catch (IOException e) {
- message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED);
- }
- mXmppConnectionService.updateMessage(message);
- break;
- case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
- synchronized (PgpDecryptionService.this) {
- PendingIntent pendingIntent = result.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
- messages.addFirst(message);
- currentMessage = null;
- storePendingIntent(pendingIntent);
- }
- break;
- case OpenPgpApi.RESULT_CODE_ERROR:
- message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED);
- mXmppConnectionService.updateMessage(message);
- break;
- }
- } else if (message.isFileOrImage()) {
- try {
- final DownloadableFile inputFile = mXmppConnectionService.getFileBackend().getFile(message, false);
- final DownloadableFile outputFile = mXmppConnectionService.getFileBackend().getFile(message, true);
- if (outputFile.getParentFile().mkdirs()) {
- Log.d(Config.LOGTAG,"created parent directories for "+outputFile.getAbsolutePath());
- }
- outputFile.createNewFile();
- InputStream is = new FileInputStream(inputFile);
- OutputStream os = new FileOutputStream(outputFile);
- Intent result = getOpenPgpApi().executeApi(params, is, os);
- switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) {
- case OpenPgpApi.RESULT_CODE_SUCCESS:
- OpenPgpMetadata openPgpMetadata = result.getParcelableExtra(OpenPgpApi.RESULT_METADATA);
- String originalFilename = openPgpMetadata.getFilename();
- String originalExtension = originalFilename == null ? null : MimeUtils.extractRelevantExtension(originalFilename);
- if (originalExtension != null && MimeUtils.extractRelevantExtension(outputFile.getName()) == null) {
- Log.d(Config.LOGTAG,"detected original filename during pgp decryption");
- final String mime = MimeUtils.guessMimeTypeFromExtension(originalExtension);
- final String filename = outputFile.getName()+"."+originalExtension;
- final File fixedFile = mXmppConnectionService.getFileBackend().getStorageLocation(filename,mime);
- if (fixedFile.getParentFile().mkdirs()) {
- Log.d(Config.LOGTAG,"created parent directories for "+fixedFile.getAbsolutePath());
- }
- synchronized (mXmppConnectionService.FILENAMES_TO_IGNORE_DELETION) {
- mXmppConnectionService.FILENAMES_TO_IGNORE_DELETION.add(outputFile.getAbsolutePath());
- }
- if (outputFile.renameTo(fixedFile)) {
- Log.d(Config.LOGTAG, "renamed " + outputFile.getAbsolutePath() + " to " + fixedFile.getAbsolutePath());
- message.setRelativeFilePath(fixedFile.getAbsolutePath());
- }
- }
- final String url = message.getFileParams().url;
- message.setEncryption(Message.ENCRYPTION_DECRYPTED);
- mXmppConnectionService.getFileBackend().updateFileParams(message, url);
- mXmppConnectionService.updateMessage(message);
- if (!inputFile.delete()) {
- Log.w(Config.LOGTAG,"unable to delete pgp encrypted source file "+inputFile.getAbsolutePath());
- }
- skipNotificationPush = true;
- mXmppConnectionService.getFileBackend().updateMediaScanner(outputFile, () -> notifyIfPending(message));
- break;
- case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
- synchronized (PgpDecryptionService.this) {
- PendingIntent pendingIntent = result.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
- messages.addFirst(message);
- currentMessage = null;
- storePendingIntent(pendingIntent);
- }
- break;
- case OpenPgpApi.RESULT_CODE_ERROR:
- message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED);
- mXmppConnectionService.updateMessage(message);
- break;
- }
- } catch (final IOException e) {
- message.setEncryption(Message.ENCRYPTION_DECRYPTION_FAILED);
- mXmppConnectionService.updateMessage(message);
- }
- }
- }
- if (!skipNotificationPush) {
- notifyIfPending(message);
- }
- }
-
- private synchronized void notifyIfPending(Message message) {
- if (pendingNotifications.remove(message)) {
- mXmppConnectionService.getNotificationService().push(message);
- }
- }
-
- private void storePendingIntent(PendingIntent pendingIntent) {
- this.pendingIntent = pendingIntent;
- mXmppConnectionService.updateConversationUi();
- }
-
- public synchronized boolean hasPendingIntent(Conversation conversation) {
- if (pendingIntent == null) {
- return false;
- } else {
- for (Message message : messages) {
- if (message.getConversation() == conversation) {
- return true;
- }
- }
- return false;
- }
- }
-
- public PendingIntent getPendingIntent() {
- return pendingIntent;
- }
-
- public boolean isConnected() {
- return getOpenPgpApi() != null;
- }
-}
diff --git a/src/main/java/eu/siacs/conversations/crypto/PgpEngine.java b/src/main/java/eu/siacs/conversations/crypto/PgpEngine.java
deleted file mode 100644
index 9652ad3eb..000000000
--- a/src/main/java/eu/siacs/conversations/crypto/PgpEngine.java
+++ /dev/null
@@ -1,291 +0,0 @@
-package eu.siacs.conversations.crypto;
-
-import android.app.PendingIntent;
-import android.content.Intent;
-import android.util.Log;
-
-import androidx.annotation.StringRes;
-
-import com.google.common.base.Joiner;
-import com.google.common.base.Splitter;
-import com.google.common.base.Strings;
-
-import org.openintents.openpgp.OpenPgpError;
-import org.openintents.openpgp.OpenPgpSignatureResult;
-import org.openintents.openpgp.util.OpenPgpApi;
-import org.openintents.openpgp.util.OpenPgpApi.IOpenPgpCallback;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.ArrayList;
-
-import eu.siacs.conversations.Config;
-import eu.siacs.conversations.R;
-import eu.siacs.conversations.entities.Account;
-import eu.siacs.conversations.entities.Contact;
-import eu.siacs.conversations.entities.Conversation;
-import eu.siacs.conversations.entities.DownloadableFile;
-import eu.siacs.conversations.entities.Message;
-import eu.siacs.conversations.persistance.FileBackend;
-import eu.siacs.conversations.services.XmppConnectionService;
-import eu.siacs.conversations.ui.UiCallback;
-import eu.siacs.conversations.utils.AsciiArmor;
-
-public class PgpEngine {
- private final OpenPgpApi api;
- private final XmppConnectionService mXmppConnectionService;
-
- public PgpEngine(OpenPgpApi api, XmppConnectionService service) {
- this.api = api;
- this.mXmppConnectionService = service;
- }
-
- private static void logError(Account account, OpenPgpError error) {
- if (error != null) {
- error.describeContents();
- Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() + ": OpenKeychain error '" + error.getMessage() + "' code=" + error.getErrorId() + " class=" + error.getClass().getName());
- } else {
- Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() + ": OpenKeychain error with no message");
- }
- }
-
- public void encrypt(final Message message, final UiCallback callback) {
- Intent params = new Intent();
- params.setAction(OpenPgpApi.ACTION_ENCRYPT);
- final Conversation conversation = (Conversation) message.getConversation();
- if (conversation.getMode() == Conversation.MODE_SINGLE) {
- long[] keys = {
- conversation.getContact().getPgpKeyId(),
- conversation.getAccount().getPgpId()
- };
- params.putExtra(OpenPgpApi.EXTRA_KEY_IDS, keys);
- } else {
- params.putExtra(OpenPgpApi.EXTRA_KEY_IDS, conversation.getMucOptions().getPgpKeyIds());
- }
-
- if (!message.needsUploading()) {
- params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
- String body;
- if (message.hasFileOnRemoteHost()) {
- body = message.getFileParams().url;
- } else {
- body = message.getBody();
- }
- InputStream is = new ByteArrayInputStream(body.getBytes());
- final OutputStream os = new ByteArrayOutputStream();
- api.executeApiAsync(params, is, os, result -> {
- switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) {
- case OpenPgpApi.RESULT_CODE_SUCCESS:
- try {
- os.flush();
- final ArrayList encryptedMessageBody = new ArrayList<>();
- final String[] lines = os.toString().split("\n");
- for (int i = 2; i < lines.length - 1; ++i) {
- if (!lines[i].contains("Version")) {
- encryptedMessageBody.add(lines[i].trim());
- }
- }
- message.setEncryptedBody(Joiner.on('\n').join(encryptedMessageBody));
- message.setEncryption(Message.ENCRYPTION_DECRYPTED);
- mXmppConnectionService.sendMessage(message);
- callback.success(message);
- } catch (IOException e) {
- callback.error(R.string.openpgp_error, message);
- }
-
- break;
- case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
- callback.userInputRequired(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), message);
- break;
- case OpenPgpApi.RESULT_CODE_ERROR:
- OpenPgpError error = result.getParcelableExtra(OpenPgpApi.RESULT_ERROR);
- String errorMessage = error != null ? error.getMessage() : null;
- @StringRes final int res;
- if (errorMessage != null && errorMessage.startsWith("Bad key for encryption")) {
- res = R.string.bad_key_for_encryption;
- } else {
- res = R.string.openpgp_error;
- }
- logError(conversation.getAccount(), error);
- callback.error(res, message);
- break;
- }
- });
- } else {
- try {
- DownloadableFile inputFile = this.mXmppConnectionService
- .getFileBackend().getFile(message, true);
- DownloadableFile outputFile = this.mXmppConnectionService
- .getFileBackend().getFile(message, false);
- outputFile.getParentFile().mkdirs();
- outputFile.createNewFile();
- final InputStream is = new FileInputStream(inputFile);
- final OutputStream os = new FileOutputStream(outputFile);
- api.executeApiAsync(params, is, os, result -> {
- switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, OpenPgpApi.RESULT_CODE_ERROR)) {
- case OpenPgpApi.RESULT_CODE_SUCCESS:
- try {
- os.flush();
- } catch (IOException ignored) {
- //ignored
- }
- FileBackend.close(os);
- mXmppConnectionService.sendMessage(message);
- callback.success(message);
- break;
- case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
- callback.userInputRequired(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), message);
- break;
- case OpenPgpApi.RESULT_CODE_ERROR:
- logError(conversation.getAccount(), result.getParcelableExtra(OpenPgpApi.RESULT_ERROR));
- callback.error(R.string.openpgp_error, message);
- break;
- }
- });
- } catch (final IOException e) {
- callback.error(R.string.openpgp_error, message);
- }
- }
- }
-
- public long fetchKeyId(final Account account, final String status, final String signature) {
- if (signature == null || api == null) {
- return 0;
- }
- final Intent params = new Intent();
- params.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
- try {
- params.putExtra(OpenPgpApi.RESULT_DETACHED_SIGNATURE, AsciiArmor.decode(signature));
- } catch (final Exception e) {
- Log.d(Config.LOGTAG, "unable to parse signature", e);
- return 0;
- }
- final InputStream is = new ByteArrayInputStream(Strings.nullToEmpty(status).getBytes());
- final ByteArrayOutputStream os = new ByteArrayOutputStream();
- final Intent result = api.executeApi(params, is, os);
- switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
- OpenPgpApi.RESULT_CODE_ERROR)) {
- case OpenPgpApi.RESULT_CODE_SUCCESS:
- final OpenPgpSignatureResult sigResult = result.getParcelableExtra(OpenPgpApi.RESULT_SIGNATURE);
- //TODO unsure that sigResult.getResult() is either 1, 2 or 3
- if (sigResult != null) {
- return sigResult.getKeyId();
- } else {
- return 0;
- }
- case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
- return 0;
- case OpenPgpApi.RESULT_CODE_ERROR:
- logError(account, result.getParcelableExtra(OpenPgpApi.RESULT_ERROR));
- return 0;
- }
- return 0;
- }
-
- public void chooseKey(final Account account, final UiCallback callback) {
- Intent p = new Intent();
- p.setAction(OpenPgpApi.ACTION_GET_SIGN_KEY_ID);
- api.executeApiAsync(p, null, null, result -> {
- switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
- case OpenPgpApi.RESULT_CODE_SUCCESS:
- callback.success(account);
- return;
- case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
- callback.userInputRequired(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), account);
- return;
- case OpenPgpApi.RESULT_CODE_ERROR:
- logError(account, result.getParcelableExtra(OpenPgpApi.RESULT_ERROR));
- callback.error(R.string.openpgp_error, account);
- }
- });
- }
-
- public void generateSignature(Intent intent, final Account account, String status, final UiCallback callback) {
- if (account.getPgpId() == 0) {
- return;
- }
- Intent params = intent == null ? new Intent() : intent;
- params.setAction(OpenPgpApi.ACTION_CLEARTEXT_SIGN);
- params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
- params.putExtra(OpenPgpApi.EXTRA_SIGN_KEY_ID, account.getPgpId());
- InputStream is = new ByteArrayInputStream(status.getBytes());
- final OutputStream os = new ByteArrayOutputStream();
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": signing status message \"" + status + "\"");
- api.executeApiAsync(params, is, os, result -> {
- switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
- case OpenPgpApi.RESULT_CODE_SUCCESS:
- final ArrayList signature = new ArrayList<>();
- try {
- os.flush();
- boolean sig = false;
- for (final String line : Splitter.on('\n').split(os.toString())) {
- if (sig) {
- if (line.contains("END PGP SIGNATURE")) {
- sig = false;
- } else {
- if (!line.contains("Version")) {
- signature.add(line.trim());
- }
- }
- }
- if (line.contains("BEGIN PGP SIGNATURE")) {
- sig = true;
- }
- }
- } catch (IOException e) {
- callback.error(R.string.openpgp_error, null);
- return;
- }
- callback.success(Joiner.on('\n').join(signature));
- return;
- case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
- callback.userInputRequired(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), status);
- return;
- case OpenPgpApi.RESULT_CODE_ERROR:
- OpenPgpError error = result.getParcelableExtra(OpenPgpApi.RESULT_ERROR);
- if (error != null && "signing subkey not found!".equals(error.getMessage())) {
- callback.error(0, null);
- } else {
- logError(account, error);
- callback.error(R.string.unable_to_connect_to_keychain, null);
- }
- }
- });
- }
-
- public void hasKey(final Contact contact, final UiCallback callback) {
- Intent params = new Intent();
- params.setAction(OpenPgpApi.ACTION_GET_KEY);
- params.putExtra(OpenPgpApi.EXTRA_KEY_ID, contact.getPgpKeyId());
- api.executeApiAsync(params, null, null, new IOpenPgpCallback() {
-
- @Override
- public void onReturn(Intent result) {
- switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
- case OpenPgpApi.RESULT_CODE_SUCCESS:
- callback.success(contact);
- return;
- case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
- callback.userInputRequired(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), contact);
- return;
- case OpenPgpApi.RESULT_CODE_ERROR:
- logError(contact.getAccount(), result.getParcelableExtra(OpenPgpApi.RESULT_ERROR));
- callback.error(R.string.openpgp_error, contact);
- }
- }
- });
- }
-
- public PendingIntent getIntentForKey(long pgpKeyId) {
- Intent params = new Intent();
- params.setAction(OpenPgpApi.ACTION_GET_KEY);
- params.putExtra(OpenPgpApi.EXTRA_KEY_ID, pgpKeyId);
- Intent result = api.executeApi(params, null, null);
- return (PendingIntent) result.getParcelableExtra(OpenPgpApi.RESULT_INTENT);
- }
-}
diff --git a/src/main/java/eu/siacs/conversations/crypto/XmppDomainVerifier.java b/src/main/java/eu/siacs/conversations/crypto/XmppDomainVerifier.java
deleted file mode 100644
index ed17c5695..000000000
--- a/src/main/java/eu/siacs/conversations/crypto/XmppDomainVerifier.java
+++ /dev/null
@@ -1,196 +0,0 @@
-package eu.siacs.conversations.crypto;
-
-import android.util.Log;
-import android.util.Pair;
-
-import com.google.common.collect.ImmutableList;
-
-import org.bouncycastle.asn1.ASN1Primitive;
-import org.bouncycastle.asn1.DERIA5String;
-import org.bouncycastle.asn1.DERTaggedObject;
-import org.bouncycastle.asn1.DERUTF8String;
-import org.bouncycastle.asn1.DLSequence;
-import org.bouncycastle.asn1.x500.RDN;
-import org.bouncycastle.asn1.x500.X500Name;
-import org.bouncycastle.asn1.x500.style.BCStyle;
-import org.bouncycastle.asn1.x500.style.IETFUtils;
-import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
-
-import java.io.IOException;
-import java.net.IDN;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.CertificateParsingException;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.Locale;
-
-import javax.net.ssl.SSLPeerUnverifiedException;
-import javax.net.ssl.SSLSession;
-
-public class XmppDomainVerifier {
-
- private static final String LOGTAG = "XmppDomainVerifier";
-
- private static final String SRV_NAME = "1.3.6.1.5.5.7.8.7";
- private static final String XMPP_ADDR = "1.3.6.1.5.5.7.8.5";
-
- private static List getCommonNames(X509Certificate certificate) {
- List domains = new ArrayList<>();
- try {
- X500Name x500name = new JcaX509CertificateHolder(certificate).getSubject();
- RDN[] rdns = x500name.getRDNs(BCStyle.CN);
- for (int i = 0; i < rdns.length; ++i) {
- domains.add(IETFUtils.valueToString(x500name.getRDNs(BCStyle.CN)[i].getFirst().getValue()));
- }
- return domains;
- } catch (CertificateEncodingException e) {
- return domains;
- }
- }
-
- private static Pair parseOtherName(byte[] otherName) {
- try {
- ASN1Primitive asn1Primitive = ASN1Primitive.fromByteArray(otherName);
- if (asn1Primitive instanceof DERTaggedObject) {
- ASN1Primitive inner = ((DERTaggedObject) asn1Primitive).getObject();
- if (inner instanceof DLSequence) {
- DLSequence sequence = (DLSequence) inner;
- if (sequence.size() >= 2 && sequence.getObjectAt(1) instanceof DERTaggedObject) {
- String oid = sequence.getObjectAt(0).toString();
- ASN1Primitive value = ((DERTaggedObject) sequence.getObjectAt(1)).getObject();
- if (value instanceof DERUTF8String) {
- return new Pair<>(oid, ((DERUTF8String) value).getString());
- } else if (value instanceof DERIA5String) {
- return new Pair<>(oid, ((DERIA5String) value).getString());
- }
- }
- }
- }
- return null;
- } catch (IOException e) {
- return null;
- }
- }
-
- public static boolean matchDomain(final String needle, final List haystack) {
- for (final String entry : haystack) {
- if (entry.startsWith("*.")) {
- int offset = 0;
- while (offset < needle.length()) {
- int i = needle.indexOf('.', offset);
- if (i < 0) {
- break;
- }
- if (needle.substring(i).equalsIgnoreCase(entry.substring(1))) {
- return true;
- }
- offset = i + 1;
- }
- } else {
- if (entry.equalsIgnoreCase(needle)) {
- return true;
- }
- }
- }
- return false;
- }
-
- public boolean verify(final String unicodeDomain, final String unicodeHostname, SSLSession sslSession) throws SSLPeerUnverifiedException {
- final String domain = IDN.toASCII(unicodeDomain);
- final String hostname = unicodeHostname == null ? null : IDN.toASCII(unicodeHostname);
- final Certificate[] chain = sslSession.getPeerCertificates();
- if (chain.length == 0 || !(chain[0] instanceof X509Certificate)) {
- return false;
- }
- final X509Certificate certificate = (X509Certificate) chain[0];
- final List commonNames = getCommonNames(certificate);
- if (isSelfSigned(certificate)) {
- if (commonNames.size() == 1 && matchDomain(domain, commonNames)) {
- Log.d(LOGTAG, "accepted CN in self signed cert as work around for " + domain);
- return true;
- }
- }
- try {
- final ValidDomains validDomains = parseValidDomains(certificate);
- Log.d(LOGTAG, "searching for " + domain + " in srvNames: " + validDomains.srvNames + " xmppAddrs: " + validDomains.xmppAddrs + " domains:" + validDomains.domains);
- if (hostname != null) {
- Log.d(LOGTAG, "also trying to verify hostname " + hostname);
- }
- return validDomains.xmppAddrs.contains(domain)
- || validDomains.srvNames.contains("_xmpp-client." + domain)
- || matchDomain(domain, validDomains.domains)
- || (hostname != null && matchDomain(hostname, validDomains.domains));
- } catch (final Exception e) {
- return false;
- }
- }
-
- public static ValidDomains parseValidDomains(final X509Certificate certificate) throws CertificateParsingException {
- final List commonNames = getCommonNames(certificate);
- final Collection> alternativeNames = certificate.getSubjectAlternativeNames();
- final List xmppAddrs = new ArrayList<>();
- final List srvNames = new ArrayList<>();
- final List domains = new ArrayList<>();
- if (alternativeNames != null) {
- for (List> san : alternativeNames) {
- final Integer type = (Integer) san.get(0);
- if (type == 0) {
- final Pair otherName = parseOtherName((byte[]) san.get(1));
- if (otherName != null && otherName.first != null && otherName.second != null) {
- switch (otherName.first) {
- case SRV_NAME:
- srvNames.add(otherName.second.toLowerCase(Locale.US));
- break;
- case XMPP_ADDR:
- xmppAddrs.add(otherName.second.toLowerCase(Locale.US));
- break;
- default:
- Log.d(LOGTAG, "oid: " + otherName.first + " value: " + otherName.second);
- }
- }
- } else if (type == 2) {
- final Object value = san.get(1);
- if (value instanceof String) {
- domains.add(((String) value).toLowerCase(Locale.US));
- }
- }
- }
- }
- if (srvNames.size() == 0 && xmppAddrs.size() == 0 && domains.size() == 0) {
- domains.addAll(commonNames);
- }
- return new ValidDomains(xmppAddrs, srvNames, domains);
- }
-
- public static final class ValidDomains {
- final List xmppAddrs;
- final List srvNames;
- final List domains;
-
- private ValidDomains(List xmppAddrs, List srvNames, List domains) {
- this.xmppAddrs = xmppAddrs;
- this.srvNames = srvNames;
- this.domains = domains;
- }
-
- public List all() {
- ImmutableList.Builder all = new ImmutableList.Builder<>();
- all.addAll(xmppAddrs);
- all.addAll(srvNames);
- all.addAll(domains);
- return all.build();
- }
- }
-
- private boolean isSelfSigned(X509Certificate certificate) {
- try {
- certificate.verify(certificate.getPublicKey());
- return true;
- } catch (Exception e) {
- return false;
- }
- }
-}
diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java
deleted file mode 100644
index 05ffdbdca..000000000
--- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java
+++ /dev/null
@@ -1,1775 +0,0 @@
-package eu.siacs.conversations.crypto.axolotl;
-
-import static eu.siacs.conversations.utils.Random.SECURE_RANDOM;
-
-import android.os.Bundle;
-import android.security.KeyChain;
-import android.util.Log;
-import android.util.Pair;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.util.concurrent.Futures;
-import com.google.common.util.concurrent.ListenableFuture;
-import com.google.common.util.concurrent.MoreExecutors;
-import com.google.common.util.concurrent.SettableFuture;
-
-import org.bouncycastle.jce.provider.BouncyCastleProvider;
-import org.whispersystems.libsignal.IdentityKey;
-import org.whispersystems.libsignal.IdentityKeyPair;
-import org.whispersystems.libsignal.InvalidKeyException;
-import org.whispersystems.libsignal.InvalidKeyIdException;
-import org.whispersystems.libsignal.SessionBuilder;
-import org.whispersystems.libsignal.SignalProtocolAddress;
-import org.whispersystems.libsignal.UntrustedIdentityException;
-import org.whispersystems.libsignal.ecc.ECPublicKey;
-import org.whispersystems.libsignal.state.PreKeyBundle;
-import org.whispersystems.libsignal.state.PreKeyRecord;
-import org.whispersystems.libsignal.state.SignedPreKeyRecord;
-import org.whispersystems.libsignal.util.KeyHelper;
-
-import java.security.PrivateKey;
-import java.security.Security;
-import java.security.Signature;
-import java.security.cert.X509Certificate;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Random;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicBoolean;
-
-import eu.siacs.conversations.Config;
-import eu.siacs.conversations.entities.Account;
-import eu.siacs.conversations.entities.Contact;
-import eu.siacs.conversations.entities.Conversation;
-import eu.siacs.conversations.entities.Message;
-import eu.siacs.conversations.parser.IqParser;
-import eu.siacs.conversations.services.XmppConnectionService;
-import eu.siacs.conversations.utils.CryptoHelper;
-import eu.siacs.conversations.utils.SerialSingleThreadExecutor;
-import eu.siacs.conversations.xml.Element;
-import eu.siacs.conversations.xml.Namespace;
-import eu.siacs.conversations.xmpp.Jid;
-import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded;
-import eu.siacs.conversations.xmpp.OnIqPacketReceived;
-import eu.siacs.conversations.xmpp.jingle.OmemoVerification;
-import eu.siacs.conversations.xmpp.jingle.OmemoVerifiedRtpContentMap;
-import eu.siacs.conversations.xmpp.jingle.RtpContentMap;
-import eu.siacs.conversations.xmpp.jingle.stanzas.IceUdpTransportInfo;
-import eu.siacs.conversations.xmpp.jingle.stanzas.OmemoVerifiedIceUdpTransportInfo;
-import eu.siacs.conversations.xmpp.pep.PublishOptions;
-import eu.siacs.conversations.xmpp.stanzas.IqPacket;
-import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
-
-public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
-
- public static final String PEP_PREFIX = "eu.siacs.conversations.axolotl";
- public static final String PEP_DEVICE_LIST = PEP_PREFIX + ".devicelist";
- public static final String PEP_DEVICE_LIST_NOTIFY = PEP_DEVICE_LIST + "+notify";
- public static final String PEP_BUNDLES = PEP_PREFIX + ".bundles";
- public static final String PEP_VERIFICATION = PEP_PREFIX + ".verification";
- public static final String PEP_OMEMO_WHITELISTED = PEP_PREFIX + ".whitelisted";
-
- public static final String LOGPREFIX = "AxolotlService";
-
- private static final int NUM_KEYS_TO_PUBLISH = 100;
- private static final int publishTriesThreshold = 3;
-
- private final Account account;
- private final XmppConnectionService mXmppConnectionService;
- private final SQLiteAxolotlStore axolotlStore;
- private final SessionMap sessions;
- private final Map> deviceIds;
- private final Map messageCache;
- private final FetchStatusMap fetchStatusMap;
- private final Map fetchDeviceListStatus = new HashMap<>();
- private final HashMap> fetchDeviceIdsMap = new HashMap<>();
- private final SerialSingleThreadExecutor executor;
- private final Set healingAttempts = new HashSet<>();
- private final HashSet cleanedOwnDeviceIds = new HashSet<>();
- private final Set PREVIOUSLY_REMOVED_FROM_ANNOUNCEMENT = new HashSet<>();
- private int numPublishTriesOnEmptyPep = 0;
- private boolean pepBroken = false;
- private int lastDeviceListNotificationHash = 0;
- private final Set postponedSessions = new HashSet<>(); //sessions stored here will receive after mam catchup treatment
- private final Set postponedHealing = new HashSet<>(); //addresses stored here will need a healing notification after mam catchup
- private final AtomicBoolean changeAccessMode = new AtomicBoolean(false);
-
- public AxolotlService(Account account, XmppConnectionService connectionService) {
- if (account == null || connectionService == null) {
- throw new IllegalArgumentException("account and service cannot be null");
- }
- if (Security.getProvider("BC") == null) {
- Security.addProvider(new BouncyCastleProvider());
- }
- this.mXmppConnectionService = connectionService;
- this.account = account;
- this.axolotlStore = new SQLiteAxolotlStore(this.account, this.mXmppConnectionService);
- this.deviceIds = new HashMap<>();
- this.messageCache = new HashMap<>();
- this.sessions = new SessionMap(mXmppConnectionService, axolotlStore, account);
- this.fetchStatusMap = new FetchStatusMap();
- this.executor = new SerialSingleThreadExecutor("Axolotl");
- }
-
- public static String getLogprefix(Account account) {
- return LOGPREFIX + " (" + account.getJid().asBareJid().toString() + "): ";
- }
-
- @Override
- public void onAdvancedStreamFeaturesAvailable(Account account) {
- if (Config.supportOmemo()
- && account.getXmppConnection() != null
- && account.getXmppConnection().getFeatures().pep()) {
- publishBundlesIfNeeded(true, false);
- } else {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": skipping OMEMO initialization");
- }
- }
-
- private boolean hasErrorFetchingDeviceList(Jid jid) {
- Boolean status = fetchDeviceListStatus.get(jid);
- return status != null && !status;
- }
-
- public boolean hasErrorFetchingDeviceList(List jids) {
- for (Jid jid : jids) {
- if (hasErrorFetchingDeviceList(jid)) {
- return true;
- }
- }
- return false;
- }
-
- public boolean fetchMapHasErrors(List jids) {
- for (Jid jid : jids) {
- if (deviceIds.get(jid) != null) {
- for (Integer foreignId : this.deviceIds.get(jid)) {
- SignalProtocolAddress address = new SignalProtocolAddress(jid.toString(), foreignId);
- if (fetchStatusMap.getAll(address.getName()).containsValue(FetchStatus.ERROR)) {
- return true;
- }
- }
- }
- }
- return false;
- }
-
- public void preVerifyFingerprint(Contact contact, String fingerprint) {
- axolotlStore.preVerifyFingerprint(contact.getAccount(), contact.getJid().asBareJid().toString(), fingerprint);
- }
-
- public void preVerifyFingerprint(Account account, String fingerprint) {
- axolotlStore.preVerifyFingerprint(account, account.getJid().asBareJid().toString(), fingerprint);
- }
-
- public boolean hasVerifiedKeys(String name) {
- for (XmppAxolotlSession session : this.sessions.getAll(name).values()) {
- if (session.getTrust().isVerified()) {
- return true;
- }
- }
- return false;
- }
-
- public String getOwnFingerprint() {
- return CryptoHelper.bytesToHex(axolotlStore.getIdentityKeyPair().getPublicKey().serialize());
- }
-
- public Set getKeysWithTrust(FingerprintStatus status) {
- return axolotlStore.getContactKeysWithTrust(account.getJid().asBareJid().toString(), status);
- }
-
- public Set getKeysWithTrust(FingerprintStatus status, Jid jid) {
- return axolotlStore.getContactKeysWithTrust(jid.asBareJid().toString(), status);
- }
-
- public Set getKeysWithTrust(FingerprintStatus status, List jids) {
- Set keys = new HashSet<>();
- for (Jid jid : jids) {
- keys.addAll(axolotlStore.getContactKeysWithTrust(jid.toString(), status));
- }
- return keys;
- }
-
- public Set findCounterpartsBySourceId(int sid) {
- return sessions.findCounterpartsForSourceId(sid);
- }
-
- public long getNumTrustedKeys(Jid jid) {
- return axolotlStore.getContactNumTrustedKeys(jid.asBareJid().toString());
- }
-
- public boolean anyTargetHasNoTrustedKeys(List jids) {
- for (Jid jid : jids) {
- if (axolotlStore.getContactNumTrustedKeys(jid.asBareJid().toString()) == 0) {
- return true;
- }
- }
- return false;
- }
-
- private SignalProtocolAddress getAddressForJid(Jid jid) {
- return new SignalProtocolAddress(jid.toString(), 0);
- }
-
- public Collection findOwnSessions() {
- SignalProtocolAddress ownAddress = getAddressForJid(account.getJid().asBareJid());
- ArrayList s = new ArrayList<>(this.sessions.getAll(ownAddress.getName()).values());
- Collections.sort(s);
- return s;
- }
-
- public Collection findSessionsForContact(Contact contact) {
- SignalProtocolAddress contactAddress = getAddressForJid(contact.getJid());
- ArrayList s = new ArrayList<>(this.sessions.getAll(contactAddress.getName()).values());
- Collections.sort(s);
- return s;
- }
-
- private Set findSessionsForConversation(Conversation conversation) {
- if (conversation.getContact().isSelf()) {
- //will be added in findOwnSessions()
- return Collections.emptySet();
- }
- HashSet sessions = new HashSet<>();
- for (Jid jid : conversation.getAcceptedCryptoTargets()) {
- sessions.addAll(this.sessions.getAll(getAddressForJid(jid).getName()).values());
- }
- return sessions;
- }
-
- private boolean hasAny(Jid jid) {
- return sessions.hasAny(getAddressForJid(jid));
- }
-
- public boolean isPepBroken() {
- return this.pepBroken;
- }
-
- public void resetBrokenness() {
- this.pepBroken = false;
- this.numPublishTriesOnEmptyPep = 0;
- this.lastDeviceListNotificationHash = 0;
- this.healingAttempts.clear();
- }
-
- public void clearErrorsInFetchStatusMap(Jid jid) {
- fetchStatusMap.clearErrorFor(jid);
- fetchDeviceListStatus.remove(jid);
- }
-
- public void regenerateKeys(boolean wipeOther) {
- axolotlStore.regenerate();
- sessions.clear();
- fetchStatusMap.clear();
- fetchDeviceIdsMap.clear();
- fetchDeviceListStatus.clear();
- publishBundlesIfNeeded(true, wipeOther);
- }
-
- public void destroy() {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": destroying old axolotl service. no longer in use");
- mXmppConnectionService.databaseBackend.wipeAxolotlDb(account);
- }
-
- public AxolotlService makeNew() {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": make new axolotl service");
- return new AxolotlService(this.account, this.mXmppConnectionService);
- }
-
- public int getOwnDeviceId() {
- return axolotlStore.getLocalRegistrationId();
- }
-
- public SignalProtocolAddress getOwnAxolotlAddress() {
- return new SignalProtocolAddress(account.getJid().asBareJid().toString(), getOwnDeviceId());
- }
-
- public Set getOwnDeviceIds() {
- return this.deviceIds.get(account.getJid().asBareJid());
- }
-
- public void registerDevices(final Jid jid, @NonNull final Set deviceIds) {
- final int hash = deviceIds.hashCode();
- final boolean me = jid.asBareJid().equals(account.getJid().asBareJid());
- if (me) {
- if (hash != 0 && hash == this.lastDeviceListNotificationHash) {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": ignoring duplicate own device id list");
- return;
- }
- this.lastDeviceListNotificationHash = hash;
- }
- boolean needsPublishing = me && !deviceIds.contains(getOwnDeviceId());
- if (me) {
- deviceIds.remove(getOwnDeviceId());
- }
- Set expiredDevices = new HashSet<>(axolotlStore.getSubDeviceSessions(jid.asBareJid().toString()));
- expiredDevices.removeAll(deviceIds);
- for (Integer deviceId : expiredDevices) {
- SignalProtocolAddress address = new SignalProtocolAddress(jid.asBareJid().toString(), deviceId);
- XmppAxolotlSession session = sessions.get(address);
- if (session != null && session.getFingerprint() != null) {
- if (session.getTrust().isActive()) {
- session.setTrust(session.getTrust().toInactive());
- }
- }
- }
- Set newDevices = new HashSet<>(deviceIds);
- for (Integer deviceId : newDevices) {
- SignalProtocolAddress address = new SignalProtocolAddress(jid.asBareJid().toString(), deviceId);
- XmppAxolotlSession session = sessions.get(address);
- if (session != null && session.getFingerprint() != null) {
- if (!session.getTrust().isActive()) {
- Log.d(Config.LOGTAG, "reactivating device with fingerprint " + session.getFingerprint());
- session.setTrust(session.getTrust().toActive());
- }
- }
- }
- if (me) {
- if (Config.OMEMO_AUTO_EXPIRY != 0) {
- needsPublishing |= deviceIds.removeAll(getExpiredDevices());
- }
- needsPublishing |= this.changeAccessMode.get();
- for (Integer deviceId : deviceIds) {
- SignalProtocolAddress ownDeviceAddress = new SignalProtocolAddress(jid.asBareJid().toString(), deviceId);
- if (sessions.get(ownDeviceAddress) == null) {
- FetchStatus status = fetchStatusMap.get(ownDeviceAddress);
- if (status == null || status == FetchStatus.TIMEOUT) {
- fetchStatusMap.put(ownDeviceAddress, FetchStatus.PENDING);
- this.buildSessionFromPEP(ownDeviceAddress);
- }
- }
- }
- if (needsPublishing) {
- publishOwnDeviceId(deviceIds);
- }
- }
- final Set oldSet = this.deviceIds.get(jid);
- final boolean changed = oldSet == null || oldSet.hashCode() != hash;
- this.deviceIds.put(jid, deviceIds);
- if (changed) {
- mXmppConnectionService.updateConversationUi(); //update the lock icon
- mXmppConnectionService.keyStatusUpdated(null);
- if (me) {
- mXmppConnectionService.updateAccountUi();
- }
- } else {
- Log.d(Config.LOGTAG, "skipped device list update because it hasn't changed");
- }
- }
-
- public void wipeOtherPepDevices() {
- if (pepBroken) {
- Log.d(Config.LOGTAG, getLogprefix(account) + "wipeOtherPepDevices called, but PEP is broken. Ignoring... ");
- return;
- }
- Set deviceIds = new HashSet<>();
- deviceIds.add(getOwnDeviceId());
- publishDeviceIdsAndRefineAccessModel(deviceIds);
- }
-
- public void distrustFingerprint(final String fingerprint) {
- final String fp = fingerprint.replaceAll("\\s", "");
- final FingerprintStatus fingerprintStatus = axolotlStore.getFingerprintStatus(fp);
- axolotlStore.setFingerprintStatus(fp, fingerprintStatus.toUntrusted());
- }
-
- private void publishOwnDeviceIdIfNeeded() {
- if (pepBroken) {
- Log.d(Config.LOGTAG, getLogprefix(account) + "publishOwnDeviceIdIfNeeded called, but PEP is broken. Ignoring... ");
- return;
- }
- IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveDeviceIds(account.getJid().asBareJid());
- mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
- @Override
- public void onIqPacketReceived(Account account, IqPacket packet) {
- if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
- Log.d(Config.LOGTAG, getLogprefix(account) + "Timeout received while retrieving own Device Ids.");
- } else {
- //TODO consider calling registerDevices only after item-not-found to account for broken PEPs
- Element item = mXmppConnectionService.getIqParser().getItem(packet);
- Set deviceIds = mXmppConnectionService.getIqParser().deviceIds(item);
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": retrieved own device list: " + deviceIds);
- registerDevices(account.getJid().asBareJid(), deviceIds);
- }
- }
- });
- }
-
- private Set getExpiredDevices() {
- Set devices = new HashSet<>();
- for (XmppAxolotlSession session : findOwnSessions()) {
- if (session.getTrust().isActive()) {
- long diff = System.currentTimeMillis() - session.getTrust().getLastActivation();
- if (diff > Config.OMEMO_AUTO_EXPIRY) {
- long lastMessageDiff = System.currentTimeMillis() - mXmppConnectionService.databaseBackend.getLastTimeFingerprintUsed(account, session.getFingerprint());
- long hours = Math.round(lastMessageDiff / (1000 * 60.0 * 60.0));
- if (lastMessageDiff > Config.OMEMO_AUTO_EXPIRY) {
- devices.add(session.getRemoteAddress().getDeviceId());
- session.setTrust(session.getTrust().toInactive());
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": added own device " + session.getFingerprint() + " to list of expired devices. Last message received " + hours + " hours ago");
- } else {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": own device " + session.getFingerprint() + " was active " + hours + " hours ago");
- }
- } //TODO print last activation diff
- }
- }
- return devices;
- }
-
- private void publishOwnDeviceId(Set deviceIds) {
- Set deviceIdsCopy = new HashSet<>(deviceIds);
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "publishing own device ids");
- if (deviceIdsCopy.isEmpty()) {
- if (numPublishTriesOnEmptyPep >= publishTriesThreshold) {
- Log.w(Config.LOGTAG, getLogprefix(account) + "Own device publish attempt threshold exceeded, aborting...");
- pepBroken = true;
- return;
- } else {
- numPublishTriesOnEmptyPep++;
- Log.w(Config.LOGTAG, getLogprefix(account) + "Own device list empty, attempting to publish (try " + numPublishTriesOnEmptyPep + ")");
- }
- } else {
- numPublishTriesOnEmptyPep = 0;
- }
- deviceIdsCopy.add(getOwnDeviceId());
- publishDeviceIdsAndRefineAccessModel(deviceIdsCopy);
- }
-
- private void publishDeviceIdsAndRefineAccessModel(Set ids) {
- publishDeviceIdsAndRefineAccessModel(ids, true);
- }
-
- private void publishDeviceIdsAndRefineAccessModel(final Set ids, final boolean firstAttempt) {
- final Bundle publishOptions = account.getXmppConnection().getFeatures().pepPublishOptions() ? PublishOptions.openAccess() : null;
- IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(ids, publishOptions);
- mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() {
- @Override
- public void onIqPacketReceived(Account account, IqPacket packet) {
- final Element error = packet.getType() == IqPacket.TYPE.ERROR ? packet.findChild("error") : null;
- final boolean preConditionNotMet = PublishOptions.preconditionNotMet(packet);
- if (firstAttempt && preConditionNotMet) {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": precondition wasn't met for device list. pushing node configuration");
- mXmppConnectionService.pushNodeConfiguration(account, AxolotlService.PEP_DEVICE_LIST, publishOptions, new XmppConnectionService.OnConfigurationPushed() {
- @Override
- public void onPushSucceeded() {
- publishDeviceIdsAndRefineAccessModel(ids, false);
- }
-
- @Override
- public void onPushFailed() {
- publishDeviceIdsAndRefineAccessModel(ids, false);
- }
- });
- } else {
- if (AxolotlService.this.changeAccessMode.compareAndSet(true, false)) {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": done changing access mode");
- account.setOption(Account.OPTION_REQUIRES_ACCESS_MODE_CHANGE, false);
- mXmppConnectionService.databaseBackend.updateAccount(account);
- }
- if (packet.getType() == IqPacket.TYPE.ERROR) {
- if (preConditionNotMet) {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": device list pre condition still not met on second attempt");
- } else if (error != null) {
- pepBroken = true;
- Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing own device id" + packet.findChild("error"));
- }
-
- }
- }
- }
- });
- }
-
- public void publishDeviceVerificationAndBundle(final SignedPreKeyRecord signedPreKeyRecord,
- final Set preKeyRecords,
- final boolean announceAfter,
- final boolean wipe) {
- try {
- IdentityKey axolotlPublicKey = axolotlStore.getIdentityKeyPair().getPublicKey();
- PrivateKey x509PrivateKey = KeyChain.getPrivateKey(mXmppConnectionService, account.getPrivateKeyAlias());
- X509Certificate[] chain = KeyChain.getCertificateChain(mXmppConnectionService, account.getPrivateKeyAlias());
- Signature verifier = Signature.getInstance("sha256WithRSA");
- verifier.initSign(x509PrivateKey, SECURE_RANDOM);
- verifier.update(axolotlPublicKey.serialize());
- byte[] signature = verifier.sign();
- IqPacket packet = mXmppConnectionService.getIqGenerator().publishVerification(signature, chain, getOwnDeviceId());
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ": publish verification for device " + getOwnDeviceId());
- mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
- @Override
- public void onIqPacketReceived(final Account account, IqPacket packet) {
- String node = AxolotlService.PEP_VERIFICATION + ":" + getOwnDeviceId();
- mXmppConnectionService.pushNodeConfiguration(account, node, PublishOptions.openAccess(), new XmppConnectionService.OnConfigurationPushed() {
- @Override
- public void onPushSucceeded() {
- Log.d(Config.LOGTAG, getLogprefix(account) + "configured verification node to be world readable");
- publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe);
- }
-
- @Override
- public void onPushFailed() {
- Log.d(Config.LOGTAG, getLogprefix(account) + "unable to set access model on verification node");
- publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe);
- }
- });
- }
- });
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
- public void publishBundlesIfNeeded(final boolean announce, final boolean wipe) {
- if (pepBroken) {
- Log.d(Config.LOGTAG, getLogprefix(account) + "publishBundlesIfNeeded called, but PEP is broken. Ignoring... ");
- return;
- }
-
- if (account.getXmppConnection().getFeatures().pepPublishOptions()) {
- this.changeAccessMode.set(account.isOptionSet(Account.OPTION_REQUIRES_ACCESS_MODE_CHANGE));
- } else {
- if (account.setOption(Account.OPTION_REQUIRES_ACCESS_MODE_CHANGE, true)) {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": server doesn’t support publish-options. setting for later access mode change");
- mXmppConnectionService.databaseBackend.updateAccount(account);
- }
- }
- if (this.changeAccessMode.get()) {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": server gained publish-options capabilities. changing access model");
- }
- IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(account.getJid().asBareJid(), getOwnDeviceId());
- mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
- @Override
- public void onIqPacketReceived(Account account, IqPacket packet) {
-
- if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
- return; //ignore timeout. do nothing
- }
-
- if (packet.getType() == IqPacket.TYPE.ERROR) {
- Element error = packet.findChild("error");
- if (error == null || !error.hasChild("item-not-found")) {
- pepBroken = true;
- Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "request for device bundles came back with something other than item-not-found" + packet);
- return;
- }
- }
-
- PreKeyBundle bundle = mXmppConnectionService.getIqParser().bundle(packet);
- Map keys = mXmppConnectionService.getIqParser().preKeyPublics(packet);
- boolean flush = false;
- if (bundle == null) {
- Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received invalid bundle:" + packet);
- bundle = new PreKeyBundle(-1, -1, -1, null, -1, null, null, null);
- flush = true;
- }
- if (keys == null) {
- Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received invalid prekeys:" + packet);
- }
- try {
- boolean changed = false;
- // Validate IdentityKey
- IdentityKeyPair identityKeyPair = axolotlStore.getIdentityKeyPair();
- if (flush || !identityKeyPair.getPublicKey().equals(bundle.getIdentityKey())) {
- Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding own IdentityKey " + identityKeyPair.getPublicKey() + " to PEP.");
- changed = true;
- }
-
- // Validate signedPreKeyRecord + ID
- SignedPreKeyRecord signedPreKeyRecord;
- int numSignedPreKeys = axolotlStore.getSignedPreKeysCount();
- try {
- signedPreKeyRecord = axolotlStore.loadSignedPreKey(bundle.getSignedPreKeyId());
- if (flush
- || !bundle.getSignedPreKey().equals(signedPreKeyRecord.getKeyPair().getPublicKey())
- || !Arrays.equals(bundle.getSignedPreKeySignature(), signedPreKeyRecord.getSignature())) {
- Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP.");
- signedPreKeyRecord = KeyHelper.generateSignedPreKey(identityKeyPair, numSignedPreKeys + 1);
- axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord);
- changed = true;
- }
- } catch (InvalidKeyIdException e) {
- Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP.");
- signedPreKeyRecord = KeyHelper.generateSignedPreKey(identityKeyPair, numSignedPreKeys + 1);
- axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord);
- changed = true;
- }
-
- // Validate PreKeys
- Set preKeyRecords = new HashSet<>();
- if (keys != null) {
- for (Integer id : keys.keySet()) {
- try {
- PreKeyRecord preKeyRecord = axolotlStore.loadPreKey(id);
- if (preKeyRecord.getKeyPair().getPublicKey().equals(keys.get(id))) {
- preKeyRecords.add(preKeyRecord);
- }
- } catch (InvalidKeyIdException ignored) {
- }
- }
- }
- int newKeys = NUM_KEYS_TO_PUBLISH - preKeyRecords.size();
- if (newKeys > 0) {
- List newRecords = KeyHelper.generatePreKeys(
- axolotlStore.getCurrentPreKeyId() + 1, newKeys);
- preKeyRecords.addAll(newRecords);
- for (PreKeyRecord record : newRecords) {
- axolotlStore.storePreKey(record.getId(), record);
- }
- changed = true;
- Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding " + newKeys + " new preKeys to PEP.");
- }
-
-
- if (changed || changeAccessMode.get()) {
- if (account.getPrivateKeyAlias() != null && Config.X509_VERIFICATION) {
- mXmppConnectionService.publishDisplayName(account);
- publishDeviceVerificationAndBundle(signedPreKeyRecord, preKeyRecords, announce, wipe);
- } else {
- publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announce, wipe);
- }
- } else {
- Log.d(Config.LOGTAG, getLogprefix(account) + "Bundle " + getOwnDeviceId() + " in PEP was current");
- if (wipe) {
- wipeOtherPepDevices();
- } else if (announce) {
- Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId());
- publishOwnDeviceIdIfNeeded();
- }
- }
- } catch (InvalidKeyException e) {
- Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Failed to publish bundle " + getOwnDeviceId() + ", reason: " + e.getMessage());
- }
- }
- });
- }
-
- private void publishDeviceBundle(SignedPreKeyRecord signedPreKeyRecord,
- Set preKeyRecords,
- final boolean announceAfter,
- final boolean wipe) {
- publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe, true);
- }
-
- private void publishDeviceBundle(final SignedPreKeyRecord signedPreKeyRecord,
- final Set preKeyRecords,
- final boolean announceAfter,
- final boolean wipe,
- final boolean firstAttempt) {
- final Bundle publishOptions = account.getXmppConnection().getFeatures().pepPublishOptions() ? PublishOptions.openAccess() : null;
- final IqPacket publish = mXmppConnectionService.getIqGenerator().publishBundles(
- signedPreKeyRecord, axolotlStore.getIdentityKeyPair().getPublicKey(),
- preKeyRecords, getOwnDeviceId(), publishOptions);
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ": Bundle " + getOwnDeviceId() + " in PEP not current. Publishing...");
- mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() {
- @Override
- public void onIqPacketReceived(final Account account, IqPacket packet) {
- final boolean preconditionNotMet = PublishOptions.preconditionNotMet(packet);
- if (firstAttempt && preconditionNotMet) {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": precondition wasn't met for bundle. pushing node configuration");
- final String node = AxolotlService.PEP_BUNDLES + ":" + getOwnDeviceId();
- mXmppConnectionService.pushNodeConfiguration(account, node, publishOptions, new XmppConnectionService.OnConfigurationPushed() {
- @Override
- public void onPushSucceeded() {
- publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe, false);
- }
-
- @Override
- public void onPushFailed() {
- publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe, false);
- }
- });
- } else if (packet.getType() == IqPacket.TYPE.RESULT) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Successfully published bundle. ");
- if (wipe) {
- wipeOtherPepDevices();
- } else if (announceAfter) {
- Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId());
- publishOwnDeviceIdIfNeeded();
- }
- } else if (packet.getType() == IqPacket.TYPE.ERROR) {
- if (preconditionNotMet) {
- Log.d(Config.LOGTAG, getLogprefix(account) + "bundle precondition still not met after second attempt");
- } else {
- Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing bundle: " + packet.toString());
- }
- pepBroken = true;
- }
- }
- });
- }
-
- public void deleteOmemoIdentity() {
- mXmppConnectionService.deletePepNode(
- account, AxolotlService.PEP_BUNDLES + ":" + getOwnDeviceId());
- final Set ownDeviceIds = getOwnDeviceIds();
- publishDeviceIdsAndRefineAccessModel(
- ownDeviceIds == null ? Collections.emptySet() : ownDeviceIds);
- }
-
- public List getCryptoTargets(Conversation conversation) {
- final List jids;
- if (conversation.getMode() == Conversation.MODE_SINGLE) {
- jids = new ArrayList<>();
- jids.add(conversation.getJid().asBareJid());
- } else {
- jids = conversation.getMucOptions().getMembers(false);
- }
- return jids;
- }
-
- public FingerprintStatus getFingerprintTrust(String fingerprint) {
- return axolotlStore.getFingerprintStatus(fingerprint);
- }
-
- public X509Certificate getFingerprintCertificate(String fingerprint) {
- return axolotlStore.getFingerprintCertificate(fingerprint);
- }
-
- public void setFingerprintTrust(String fingerprint, FingerprintStatus status) {
- axolotlStore.setFingerprintStatus(fingerprint, status);
- }
-
- private ListenableFuture verifySessionWithPEP(final XmppAxolotlSession session) {
- Log.d(Config.LOGTAG, "trying to verify fresh session (" + session.getRemoteAddress().getName() + ") with pep");
- final SignalProtocolAddress address = session.getRemoteAddress();
- final IdentityKey identityKey = session.getIdentityKey();
- final Jid jid;
- try {
- jid = Jid.of(address.getName());
- } catch (final IllegalArgumentException e) {
- fetchStatusMap.put(address, FetchStatus.SUCCESS);
- finishBuildingSessionsFromPEP(address);
- return Futures.immediateFuture(session);
- }
- final SettableFuture future = SettableFuture.create();
- final IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveVerificationForDevice(jid, address.getDeviceId());
- mXmppConnectionService.sendIqPacket(account, packet, (account, response) -> {
- Pair verification = mXmppConnectionService.getIqParser().verification(response);
- if (verification != null) {
- try {
- Signature verifier = Signature.getInstance("sha256WithRSA");
- verifier.initVerify(verification.first[0]);
- verifier.update(identityKey.serialize());
- if (verifier.verify(verification.second)) {
- try {
- mXmppConnectionService.getMemorizingTrustManager().getNonInteractive().checkClientTrusted(verification.first, "RSA");
- String fingerprint = session.getFingerprint();
- Log.d(Config.LOGTAG, "verified session with x.509 signature. fingerprint was: " + fingerprint);
- setFingerprintTrust(fingerprint, FingerprintStatus.createActiveVerified(true));
- axolotlStore.setFingerprintCertificate(fingerprint, verification.first[0]);
- fetchStatusMap.put(address, FetchStatus.SUCCESS_VERIFIED);
- Bundle information = CryptoHelper.extractCertificateInformation(verification.first[0]);
- try {
- final String cn = information.getString("subject_cn");
- final Jid jid1 = Jid.of(address.getName());
- Log.d(Config.LOGTAG, "setting common name for " + jid1 + " to " + cn);
- account.getRoster().getContact(jid1).setCommonName(cn);
- } catch (final IllegalArgumentException ignored) {
- //ignored
- }
- finishBuildingSessionsFromPEP(address);
- future.set(session);
- return;
- } catch (Exception e) {
- Log.d(Config.LOGTAG, "could not verify certificate");
- }
- }
- } catch (Exception e) {
- Log.d(Config.LOGTAG, "error during verification " + e.getMessage());
- }
- } else {
- Log.d(Config.LOGTAG, "no verification found");
- }
- fetchStatusMap.put(address, FetchStatus.SUCCESS);
- finishBuildingSessionsFromPEP(address);
- future.set(session);
- });
- return future;
- }
-
- private void finishBuildingSessionsFromPEP(final SignalProtocolAddress address) {
- SignalProtocolAddress ownAddress = new SignalProtocolAddress(account.getJid().asBareJid().toString(), 0);
- Map own = fetchStatusMap.getAll(ownAddress.getName());
- Map remote = fetchStatusMap.getAll(address.getName());
- if (!own.containsValue(FetchStatus.PENDING) && !remote.containsValue(FetchStatus.PENDING)) {
- FetchStatus report = null;
- if (own.containsValue(FetchStatus.SUCCESS) || remote.containsValue(FetchStatus.SUCCESS)) {
- report = FetchStatus.SUCCESS;
- } else if (own.containsValue(FetchStatus.SUCCESS_VERIFIED) || remote.containsValue(FetchStatus.SUCCESS_VERIFIED)) {
- report = FetchStatus.SUCCESS_VERIFIED;
- } else if (own.containsValue(FetchStatus.SUCCESS_TRUSTED) || remote.containsValue(FetchStatus.SUCCESS_TRUSTED)) {
- report = FetchStatus.SUCCESS_TRUSTED;
- } else if (own.containsValue(FetchStatus.ERROR) || remote.containsValue(FetchStatus.ERROR)) {
- report = FetchStatus.ERROR;
- }
- mXmppConnectionService.keyStatusUpdated(report);
- }
- if (Config.REMOVE_BROKEN_DEVICES) {
- Set ownDeviceIds = new HashSet<>(getOwnDeviceIds());
- boolean publish = false;
- for (Map.Entry entry : own.entrySet()) {
- int id = entry.getKey();
- if (entry.getValue() == FetchStatus.ERROR && PREVIOUSLY_REMOVED_FROM_ANNOUNCEMENT.add(id) && ownDeviceIds.remove(id)) {
- publish = true;
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": error fetching own device with id " + id + ". removing from announcement");
- }
- }
- if (publish) {
- publishOwnDeviceId(ownDeviceIds);
- }
- }
- }
-
- public boolean hasEmptyDeviceList(Jid jid) {
- return !hasAny(jid) && (!deviceIds.containsKey(jid) || deviceIds.get(jid).isEmpty());
- }
-
- public void fetchDeviceIds(final Jid jid) {
- fetchDeviceIds(jid, null);
- }
-
- private void fetchDeviceIds(final Jid jid, OnDeviceIdsFetched callback) {
- IqPacket packet;
- synchronized (this.fetchDeviceIdsMap) {
- List callbacks = this.fetchDeviceIdsMap.get(jid);
- if (callbacks != null) {
- if (callback != null) {
- callbacks.add(callback);
- }
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": fetching device ids for " + jid + " already running. adding callback");
- packet = null;
- } else {
- callbacks = new ArrayList<>();
- if (callback != null) {
- callbacks.add(callback);
- }
- this.fetchDeviceIdsMap.put(jid, callbacks);
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": fetching device ids for " + jid);
- packet = mXmppConnectionService.getIqGenerator().retrieveDeviceIds(jid);
- }
- }
- if (packet != null) {
- mXmppConnectionService.sendIqPacket(account, packet, (account, response) -> {
- if (response.getType() == IqPacket.TYPE.RESULT) {
- fetchDeviceListStatus.put(jid, true);
- Element item = mXmppConnectionService.getIqParser().getItem(response);
- Set deviceIds = mXmppConnectionService.getIqParser().deviceIds(item);
- registerDevices(jid, deviceIds);
- final List callbacks;
- synchronized (fetchDeviceIdsMap) {
- callbacks = fetchDeviceIdsMap.remove(jid);
- }
- if (callbacks != null) {
- for (OnDeviceIdsFetched c : callbacks) {
- c.fetched(jid, deviceIds);
- }
- }
- } else {
- if (response.getType() == IqPacket.TYPE.TIMEOUT) {
- fetchDeviceListStatus.remove(jid);
- } else {
- fetchDeviceListStatus.put(jid, false);
- }
- final List callbacks;
- synchronized (fetchDeviceIdsMap) {
- callbacks = fetchDeviceIdsMap.remove(jid);
- }
- if (callbacks != null) {
- for (OnDeviceIdsFetched c : callbacks) {
- c.fetched(jid, null);
- }
- }
- }
- });
- }
- }
-
- private void fetchDeviceIds(List jids, final OnMultipleDeviceIdFetched callback) {
- final ArrayList unfinishedJids = new ArrayList<>(jids);
- synchronized (unfinishedJids) {
- for (Jid jid : unfinishedJids) {
- fetchDeviceIds(jid, (j, deviceIds) -> {
- synchronized (unfinishedJids) {
- unfinishedJids.remove(j);
- if (unfinishedJids.size() == 0 && callback != null) {
- callback.fetched();
- }
- }
- });
- }
- }
- }
-
- private ListenableFuture buildSessionFromPEP(final SignalProtocolAddress address) {
- return buildSessionFromPEP(address, null);
- }
-
- private ListenableFuture buildSessionFromPEP(final SignalProtocolAddress address, OnSessionBuildFromPep callback) {
- final SettableFuture sessionSettableFuture = SettableFuture.create();
- Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building new session for " + address.toString());
- if (address.equals(getOwnAxolotlAddress())) {
- throw new AssertionError("We should NEVER build a session with ourselves. What happened here?!");
- }
- final Jid jid = Jid.of(address.getName());
- final boolean oneOfOurs = jid.asBareJid().equals(account.getJid().asBareJid());
- IqPacket bundlesPacket = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(jid, address.getDeviceId());
- mXmppConnectionService.sendIqPacket(account, bundlesPacket, (account, packet) -> {
- if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
- fetchStatusMap.put(address, FetchStatus.TIMEOUT);
- sessionSettableFuture.setException(new CryptoFailedException("Unable to build session. Timeout"));
- } else if (packet.getType() == IqPacket.TYPE.RESULT) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received preKey IQ packet, processing...");
- final IqParser parser = mXmppConnectionService.getIqParser();
- final List preKeyBundleList = parser.preKeys(packet);
- final PreKeyBundle bundle = parser.bundle(packet);
- if (preKeyBundleList.isEmpty() || bundle == null) {
- Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "preKey IQ packet invalid: " + packet);
- fetchStatusMap.put(address, FetchStatus.ERROR);
- finishBuildingSessionsFromPEP(address);
- if (callback != null) {
- callback.onSessionBuildFailed();
- }
- sessionSettableFuture.setException(new CryptoFailedException("Unable to build session. IQ Packet Invalid"));
- return;
- }
- Random random = new Random();
- final PreKeyBundle preKey = preKeyBundleList.get(random.nextInt(preKeyBundleList.size()));
- if (preKey == null) {
- //should never happen
- fetchStatusMap.put(address, FetchStatus.ERROR);
- finishBuildingSessionsFromPEP(address);
- if (callback != null) {
- callback.onSessionBuildFailed();
- }
- sessionSettableFuture.setException(new CryptoFailedException("Unable to build session. No suitable PreKey found"));
- return;
- }
-
- final PreKeyBundle preKeyBundle = new PreKeyBundle(0, address.getDeviceId(),
- preKey.getPreKeyId(), preKey.getPreKey(),
- bundle.getSignedPreKeyId(), bundle.getSignedPreKey(),
- bundle.getSignedPreKeySignature(), bundle.getIdentityKey());
-
- try {
- SessionBuilder builder = new SessionBuilder(axolotlStore, address);
- builder.process(preKeyBundle);
- XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, bundle.getIdentityKey());
- sessions.put(address, session);
- if (Config.X509_VERIFICATION) {
- sessionSettableFuture.setFuture(verifySessionWithPEP(session)); //TODO; maybe inject callback in here too
- } else {
- FingerprintStatus status = getFingerprintTrust(CryptoHelper.bytesToHex(bundle.getIdentityKey().getPublicKey().serialize()));
- FetchStatus fetchStatus;
- if (status != null && status.isVerified()) {
- fetchStatus = FetchStatus.SUCCESS_VERIFIED;
- } else if (status != null && status.isTrusted()) {
- fetchStatus = FetchStatus.SUCCESS_TRUSTED;
- } else {
- fetchStatus = FetchStatus.SUCCESS;
- }
- fetchStatusMap.put(address, fetchStatus);
- finishBuildingSessionsFromPEP(address);
- if (callback != null) {
- callback.onSessionBuildSuccessful();
- }
- sessionSettableFuture.set(session);
- }
- } catch (UntrustedIdentityException | InvalidKeyException e) {
- Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error building session for " + address + ": "
- + e.getClass().getName() + ", " + e.getMessage());
- fetchStatusMap.put(address, FetchStatus.ERROR);
- finishBuildingSessionsFromPEP(address);
- if (oneOfOurs && cleanedOwnDeviceIds.add(address.getDeviceId())) {
- removeFromDeviceAnnouncement(address.getDeviceId());
- }
- if (callback != null) {
- callback.onSessionBuildFailed();
- }
- sessionSettableFuture.setException(new CryptoFailedException(e));
- }
- } else {
- fetchStatusMap.put(address, FetchStatus.ERROR);
- Element error = packet.findChild("error");
- boolean itemNotFound = error != null && error.hasChild("item-not-found");
- Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while building session:" + packet.findChild("error"));
- finishBuildingSessionsFromPEP(address);
- if (oneOfOurs && itemNotFound && cleanedOwnDeviceIds.add(address.getDeviceId())) {
- removeFromDeviceAnnouncement(address.getDeviceId());
- }
- if (callback != null) {
- callback.onSessionBuildFailed();
- }
- sessionSettableFuture.setException(new CryptoFailedException("Unable to build session. IQ Packet Error"));
- }
- });
- return sessionSettableFuture;
- }
-
- private void removeFromDeviceAnnouncement(Integer id) {
- HashSet temp = new HashSet<>(getOwnDeviceIds());
- if (temp.remove(id)) {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + " remove own device id " + id + " from announcement. devices left:" + temp);
- publishOwnDeviceId(temp);
- }
- }
-
- public Set findDevicesWithoutSession(final Conversation conversation) {
- Set addresses = new HashSet<>();
- for (Jid jid : getCryptoTargets(conversation)) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Finding devices without session for " + jid);
- final Set ids = deviceIds.get(jid);
- if (ids != null && !ids.isEmpty()) {
- for (Integer foreignId : ids) {
- SignalProtocolAddress address = new SignalProtocolAddress(jid.toString(), foreignId);
- if (sessions.get(address) == null) {
- IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
- if (identityKey != null) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already have session for " + address.toString() + ", adding to cache...");
- XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey);
- sessions.put(address, session);
- } else {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Found device " + jid + ":" + foreignId);
- if (fetchStatusMap.get(address) != FetchStatus.ERROR) {
- addresses.add(address);
- } else {
- Log.d(Config.LOGTAG, getLogprefix(account) + "skipping over " + address + " because it's broken");
- }
- }
- }
- }
- } else {
- mXmppConnectionService.keyStatusUpdated(FetchStatus.ERROR);
- Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Have no target devices in PEP!");
- }
- }
- Set ownIds = this.deviceIds.get(account.getJid().asBareJid());
- for (Integer ownId : (ownIds != null ? ownIds : new HashSet())) {
- SignalProtocolAddress address = new SignalProtocolAddress(account.getJid().asBareJid().toString(), ownId);
- if (sessions.get(address) == null) {
- IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
- if (identityKey != null) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already have session for " + address.toString() + ", adding to cache...");
- XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey);
- sessions.put(address, session);
- } else {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Found device " + account.getJid().asBareJid() + ":" + ownId);
- if (fetchStatusMap.get(address) != FetchStatus.ERROR) {
- addresses.add(address);
- } else {
- Log.d(Config.LOGTAG, getLogprefix(account) + "skipping over " + address + " because it's broken");
- }
- }
- }
- }
-
- return addresses;
- }
-
- public boolean createSessionsIfNeeded(final Conversation conversation) {
- final List jidsWithEmptyDeviceList = getCryptoTargets(conversation);
- for (Iterator iterator = jidsWithEmptyDeviceList.iterator(); iterator.hasNext(); ) {
- final Jid jid = iterator.next();
- if (!hasEmptyDeviceList(jid)) {
- iterator.remove();
- }
- }
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": createSessionsIfNeeded() - jids with empty device list: " + jidsWithEmptyDeviceList);
- if (jidsWithEmptyDeviceList.size() > 0) {
- fetchDeviceIds(jidsWithEmptyDeviceList, () -> createSessionsIfNeededActual(conversation));
- return true;
- } else {
- return createSessionsIfNeededActual(conversation);
- }
- }
-
- private boolean createSessionsIfNeededActual(final Conversation conversation) {
- Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Creating axolotl sessions if needed...");
- boolean newSessions = false;
- Set addresses = findDevicesWithoutSession(conversation);
- for (SignalProtocolAddress address : addresses) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Processing device: " + address.toString());
- FetchStatus status = fetchStatusMap.get(address);
- if (status == null || status == FetchStatus.TIMEOUT) {
- fetchStatusMap.put(address, FetchStatus.PENDING);
- this.buildSessionFromPEP(address);
- newSessions = true;
- } else if (status == FetchStatus.PENDING) {
- newSessions = true;
- } else {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already fetching bundle for " + address.toString());
- }
- }
-
- return newSessions;
- }
-
- public boolean trustedSessionVerified(final Conversation conversation) {
- final Set sessions = new HashSet<>();
- sessions.addAll(findSessionsForConversation(conversation));
- sessions.addAll(findOwnSessions());
- boolean verified = false;
- for (XmppAxolotlSession session : sessions) {
- if (session.getTrust().isTrustedAndActive()) {
- if (session.getTrust().getTrust() == FingerprintStatus.Trust.VERIFIED_X509) {
- verified = true;
- } else {
- return false;
- }
- }
- }
- return verified;
- }
-
- public boolean hasPendingKeyFetches(List jids) {
- SignalProtocolAddress ownAddress = new SignalProtocolAddress(account.getJid().asBareJid().toString(), 0);
- if (fetchStatusMap.getAll(ownAddress.getName()).containsValue(FetchStatus.PENDING)) {
- return true;
- }
- synchronized (this.fetchDeviceIdsMap) {
- for (Jid jid : jids) {
- SignalProtocolAddress foreignAddress = new SignalProtocolAddress(jid.asBareJid().toString(), 0);
- if (fetchStatusMap.getAll(foreignAddress.getName()).containsValue(FetchStatus.PENDING) || this.fetchDeviceIdsMap.containsKey(jid)) {
- return true;
- }
- }
- }
- return false;
- }
-
- @Nullable
- private boolean buildHeader(XmppAxolotlMessage axolotlMessage, Conversation c) {
- Set remoteSessions = findSessionsForConversation(c);
- final boolean acceptEmpty = (c.getMode() == Conversation.MODE_MULTI && c.getMucOptions().getUserCount() == 0) || c.getContact().isSelf();
- Collection ownSessions = findOwnSessions();
- if (remoteSessions.isEmpty() && !acceptEmpty) {
- return false;
- }
- for (XmppAxolotlSession session : remoteSessions) {
- axolotlMessage.addDevice(session);
- }
- for (XmppAxolotlSession session : ownSessions) {
- axolotlMessage.addDevice(session);
- }
-
- return true;
- }
-
- //this is being used for private muc messages only
- private boolean buildHeader(XmppAxolotlMessage axolotlMessage, Jid jid) {
- if (jid == null) {
- return false;
- }
- HashSet sessions = new HashSet<>();
- sessions.addAll(this.sessions.getAll(getAddressForJid(jid).getName()).values());
- if (sessions.isEmpty()) {
- return false;
- }
- sessions.addAll(findOwnSessions());
- for (XmppAxolotlSession session : sessions) {
- axolotlMessage.addDevice(session);
- }
- return true;
- }
-
- @Nullable
- public XmppAxolotlMessage encrypt(Message message) {
- final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(account.getJid().asBareJid(), getOwnDeviceId());
- final String content;
- if (message.hasFileOnRemoteHost()) {
- content = message.getFileParams().url;
- } else {
- content = message.getBody();
- }
- try {
- axolotlMessage.encrypt(content);
- } catch (CryptoFailedException e) {
- Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to encrypt message: " + e.getMessage());
- return null;
- }
-
- final boolean success;
- if (message.isPrivateMessage()) {
- success = buildHeader(axolotlMessage, message.getTrueCounterpart());
- } else {
- success = buildHeader(axolotlMessage, (Conversation) message.getConversation());
- }
- return success ? axolotlMessage : null;
- }
-
- public void preparePayloadMessage(final Message message, final boolean delay) {
- executor.execute(new Runnable() {
- @Override
- public void run() {
- XmppAxolotlMessage axolotlMessage = encrypt(message);
- if (axolotlMessage == null) {
- mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED);
- //mXmppConnectionService.updateConversationUi();
- } else {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Generated message, caching: " + message.getUuid());
- messageCache.put(message.getUuid(), axolotlMessage);
- mXmppConnectionService.resendMessage(message, delay);
- }
- }
- });
- }
-
- private OmemoVerifiedIceUdpTransportInfo encrypt(final IceUdpTransportInfo element, final XmppAxolotlSession session) throws CryptoFailedException {
- final OmemoVerifiedIceUdpTransportInfo transportInfo = new OmemoVerifiedIceUdpTransportInfo();
- transportInfo.setAttributes(element.getAttributes());
- for (final Element child : element.getChildren()) {
- if ("fingerprint".equals(child.getName()) && Namespace.JINGLE_APPS_DTLS.equals(child.getNamespace())) {
- final Element fingerprint = new Element("fingerprint", Namespace.OMEMO_DTLS_SRTP_VERIFICATION);
- fingerprint.setAttribute("setup", child.getAttribute("setup"));
- fingerprint.setAttribute("hash", child.getAttribute("hash"));
- final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(account.getJid().asBareJid(), getOwnDeviceId());
- final String content = child.getContent();
- axolotlMessage.encrypt(content);
- axolotlMessage.addDevice(session, true);
- fingerprint.addChild(axolotlMessage.toElement());
- transportInfo.addChild(fingerprint);
- } else {
- transportInfo.addChild(child);
- }
- }
- return transportInfo;
- }
-
-
- public ListenableFuture> encrypt(final RtpContentMap rtpContentMap, final Jid jid, final int deviceId) {
- return Futures.transformAsync(
- getSession(jid, deviceId),
- session -> encrypt(rtpContentMap, session),
- MoreExecutors.directExecutor()
- );
- }
-
- private ListenableFuture> encrypt(final RtpContentMap rtpContentMap, final XmppAxolotlSession session) {
- if (Config.REQUIRE_RTP_VERIFICATION) {
- requireVerification(session);
- }
- final ImmutableMap.Builder descriptionTransportBuilder = new ImmutableMap.Builder<>();
- final OmemoVerification omemoVerification = new OmemoVerification();
- omemoVerification.setDeviceId(session.getRemoteAddress().getDeviceId());
- omemoVerification.setSessionFingerprint(session.getFingerprint());
- for (final Map.Entry content : rtpContentMap.contents.entrySet()) {
- final RtpContentMap.DescriptionTransport descriptionTransport = content.getValue();
- final OmemoVerifiedIceUdpTransportInfo encryptedTransportInfo;
- try {
- encryptedTransportInfo = encrypt(descriptionTransport.transport, session);
- } catch (final CryptoFailedException e) {
- return Futures.immediateFailedFuture(e);
- }
- descriptionTransportBuilder.put(
- content.getKey(),
- new RtpContentMap.DescriptionTransport(descriptionTransport.senders, descriptionTransport.description, encryptedTransportInfo)
- );
- }
- return Futures.immediateFuture(
- new OmemoVerifiedPayload<>(
- omemoVerification,
- new OmemoVerifiedRtpContentMap(rtpContentMap.group, descriptionTransportBuilder.build())
- ));
- }
-
- private ListenableFuture getSession(final Jid jid, final int deviceId) {
- final SignalProtocolAddress address = new SignalProtocolAddress(jid.asBareJid().toString(), deviceId);
- final XmppAxolotlSession session = sessions.get(address);
- if (session == null) {
- return buildSessionFromPEP(address);
- }
- return Futures.immediateFuture(session);
- }
-
- public ListenableFuture> decrypt(OmemoVerifiedRtpContentMap omemoVerifiedRtpContentMap, final Jid from) {
- final ImmutableMap.Builder descriptionTransportBuilder = new ImmutableMap.Builder<>();
- final OmemoVerification omemoVerification = new OmemoVerification();
- final ImmutableList.Builder> pepVerificationFutures = new ImmutableList.Builder<>();
- for (final Map.Entry content : omemoVerifiedRtpContentMap.contents.entrySet()) {
- final RtpContentMap.DescriptionTransport descriptionTransport = content.getValue();
- final OmemoVerifiedPayload decryptedTransport;
- try {
- decryptedTransport = decrypt((OmemoVerifiedIceUdpTransportInfo) descriptionTransport.transport, from, pepVerificationFutures);
- } catch (CryptoFailedException e) {
- return Futures.immediateFailedFuture(e);
- }
- omemoVerification.setOrEnsureEqual(decryptedTransport);
- descriptionTransportBuilder.put(
- content.getKey(),
- new RtpContentMap.DescriptionTransport(descriptionTransport.senders, descriptionTransport.description, decryptedTransport.payload)
- );
- }
- processPostponed();
- final ImmutableList> sessionFutures = pepVerificationFutures.build();
- return Futures.transform(
- Futures.allAsList(sessionFutures),
- sessions -> {
- if (Config.REQUIRE_RTP_VERIFICATION) {
- for (XmppAxolotlSession session : sessions) {
- requireVerification(session);
- }
- }
- return new OmemoVerifiedPayload<>(
- omemoVerification,
- new RtpContentMap(omemoVerifiedRtpContentMap.group, descriptionTransportBuilder.build())
- );
-
- },
- MoreExecutors.directExecutor()
- );
- }
-
- private OmemoVerifiedPayload decrypt(final OmemoVerifiedIceUdpTransportInfo verifiedIceUdpTransportInfo, final Jid from, ImmutableList.Builder> pepVerificationFutures) throws CryptoFailedException {
- final IceUdpTransportInfo transportInfo = new IceUdpTransportInfo();
- transportInfo.setAttributes(verifiedIceUdpTransportInfo.getAttributes());
- final OmemoVerification omemoVerification = new OmemoVerification();
- for (final Element child : verifiedIceUdpTransportInfo.getChildren()) {
- if ("fingerprint".equals(child.getName()) && Namespace.OMEMO_DTLS_SRTP_VERIFICATION.equals(child.getNamespace())) {
- final Element fingerprint = new Element("fingerprint", Namespace.JINGLE_APPS_DTLS);
- fingerprint.setAttribute("setup", child.getAttribute("setup"));
- fingerprint.setAttribute("hash", child.getAttribute("hash"));
- final Element encrypted = child.findChildEnsureSingle(XmppAxolotlMessage.CONTAINERTAG, AxolotlService.PEP_PREFIX);
- final XmppAxolotlMessage xmppAxolotlMessage = XmppAxolotlMessage.fromElement(encrypted, from.asBareJid());
- final XmppAxolotlSession session = getReceivingSession(xmppAxolotlMessage);
- final XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintext = xmppAxolotlMessage.decrypt(session, getOwnDeviceId());
- final Integer preKeyId = session.getPreKeyIdAndReset();
- if (preKeyId != null) {
- postponedSessions.add(session);
- }
- if (session.isFresh()) {
- pepVerificationFutures.add(putFreshSession(session));
- } else if (Config.REQUIRE_RTP_VERIFICATION) {
- pepVerificationFutures.add(Futures.immediateFuture(session));
- }
- fingerprint.setContent(plaintext.getPlaintext());
- omemoVerification.setDeviceId(session.getRemoteAddress().getDeviceId());
- omemoVerification.setSessionFingerprint(plaintext.getFingerprint());
- transportInfo.addChild(fingerprint);
- } else {
- transportInfo.addChild(child);
- }
- }
- return new OmemoVerifiedPayload<>(omemoVerification, transportInfo);
- }
-
- private static void requireVerification(final XmppAxolotlSession session) {
- if (session.getTrust().isVerified()) {
- return;
- }
- throw new NotVerifiedException(String.format(
- "session with %s was not verified",
- session.getFingerprint()
- ));
- }
-
- public void prepareKeyTransportMessage(final Conversation conversation, final OnMessageCreatedCallback onMessageCreatedCallback) {
- executor.execute(new Runnable() {
- @Override
- public void run() {
- final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(account.getJid().asBareJid(), getOwnDeviceId());
- if (buildHeader(axolotlMessage, conversation)) {
- onMessageCreatedCallback.run(axolotlMessage);
- } else {
- onMessageCreatedCallback.run(null);
- }
- }
- });
- }
-
- public XmppAxolotlMessage fetchAxolotlMessageFromCache(Message message) {
- XmppAxolotlMessage axolotlMessage = messageCache.get(message.getUuid());
- if (axolotlMessage != null) {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Cache hit: " + message.getUuid());
- messageCache.remove(message.getUuid());
- } else {
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Cache miss: " + message.getUuid());
- }
- return axolotlMessage;
- }
-
- private XmppAxolotlSession recreateUncachedSession(SignalProtocolAddress address) {
- IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
- return (identityKey != null)
- ? new XmppAxolotlSession(account, axolotlStore, address, identityKey)
- : null;
- }
-
- private XmppAxolotlSession getReceivingSession(XmppAxolotlMessage message) {
- SignalProtocolAddress senderAddress = new SignalProtocolAddress(message.getFrom().toString(), message.getSenderDeviceId());
- return getReceivingSession(senderAddress);
-
- }
-
- private XmppAxolotlSession getReceivingSession(SignalProtocolAddress senderAddress) {
- XmppAxolotlSession session = sessions.get(senderAddress);
- if (session == null) {
- session = recreateUncachedSession(senderAddress);
- if (session == null) {
- session = new XmppAxolotlSession(account, axolotlStore, senderAddress);
- }
- }
- return session;
- }
-
- public XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceivingPayloadMessage(XmppAxolotlMessage message, boolean postponePreKeyMessageHandling) throws NotEncryptedForThisDeviceException, BrokenSessionException, OutdatedSenderException {
- XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = null;
-
- XmppAxolotlSession session = getReceivingSession(message);
- int ownDeviceId = getOwnDeviceId();
- try {
- plaintextMessage = message.decrypt(session, ownDeviceId);
- Integer preKeyId = session.getPreKeyIdAndReset();
- if (preKeyId != null) {
- postPreKeyMessageHandling(session, postponePreKeyMessageHandling);
- }
- } catch (NotEncryptedForThisDeviceException e) {
- if (account.getJid().asBareJid().equals(message.getFrom().asBareJid()) && message.getSenderDeviceId() == ownDeviceId) {
- Log.w(Config.LOGTAG, getLogprefix(account) + "Reflected omemo message received");
- } else {
- throw e;
- }
- } catch (final BrokenSessionException e) {
- throw e;
- } catch (final OutdatedSenderException e) {
- Log.e(Config.LOGTAG, account.getJid().asBareJid() + ": " + e.getMessage());
- throw e;
- } catch (CryptoFailedException e) {
- Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to decrypt message from " + message.getFrom(), e);
- }
-
- if (session.isFresh() && plaintextMessage != null) {
- putFreshSession(session);
- }
-
- return plaintextMessage;
- }
-
- public void reportBrokenSessionException(BrokenSessionException e, boolean postpone) {
- Log.e(Config.LOGTAG, account.getJid().asBareJid() + ": broken session with " + e.getSignalProtocolAddress().toString() + " detected", e);
- if (postpone) {
- postponedHealing.add(e.getSignalProtocolAddress());
- } else {
- notifyRequiresHealing(e.getSignalProtocolAddress());
- }
- }
-
- private void notifyRequiresHealing(final SignalProtocolAddress signalProtocolAddress) {
- if (healingAttempts.add(signalProtocolAddress)) {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": attempt to heal " + signalProtocolAddress);
- buildSessionFromPEP(signalProtocolAddress, new OnSessionBuildFromPep() {
- @Override
- public void onSessionBuildSuccessful() {
- Log.d(Config.LOGTAG, "successfully build new session from pep after detecting broken session");
- completeSession(getReceivingSession(signalProtocolAddress));
- }
-
- @Override
- public void onSessionBuildFailed() {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": unable to build new session from pep after detecting broken session");
- }
- });
- } else {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": do not attempt to heal " + signalProtocolAddress + " again");
- }
- }
-
- private void postPreKeyMessageHandling(final XmppAxolotlSession session, final boolean postpone) {
- if (postpone) {
- postponedSessions.add(session);
- } else {
- if (axolotlStore.flushPreKeys()) {
- publishBundlesIfNeeded(false, false);
- } else {
- Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": nothing to flush. Not republishing key");
- }
- if (trustedOrPreviouslyResponded(session) && Config.AUTOMATICALLY_COMPLETE_SESSIONS) {
- completeSession(session);
- }
- }
- }
-
- public void processPostponed() {
- if (postponedSessions.size() > 0) {
- if (axolotlStore.flushPreKeys()) {
- publishBundlesIfNeeded(false, false);
- }
- }
- final Iterator iterator = postponedSessions.iterator();
- while (iterator.hasNext()) {
- final XmppAxolotlSession session = iterator.next();
- if (trustedOrPreviouslyResponded(session) && Config.AUTOMATICALLY_COMPLETE_SESSIONS) {
- completeSession(session);
- }
- iterator.remove();
- }
- final Iterator postponedHealingAttemptsIterator = postponedHealing.iterator();
- while (postponedHealingAttemptsIterator.hasNext()) {
- notifyRequiresHealing(postponedHealingAttemptsIterator.next());
- postponedHealingAttemptsIterator.remove();
- }
- }
-
- private boolean trustedOrPreviouslyResponded(XmppAxolotlSession session) {
- try {
- return trustedOrPreviouslyResponded(Jid.of(session.getRemoteAddress().getName()));
- } catch (IllegalArgumentException e) {
- return false;
- }
- }
-
- public boolean trustedOrPreviouslyResponded(Jid jid) {
- final Contact contact = account.getRoster().getContact(jid);
- if (contact.showInRoster() || contact.isSelf()) {
- return true;
- }
- final Conversation conversation = mXmppConnectionService.find(account, jid);
- return conversation != null && conversation.sentMessagesCount() > 0;
- }
-
- private void completeSession(XmppAxolotlSession session) {
- final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(account.getJid().asBareJid(), getOwnDeviceId());
- axolotlMessage.addDevice(session, true);
- try {
- final Jid jid = Jid.of(session.getRemoteAddress().getName());
- MessagePacket packet = mXmppConnectionService.getMessageGenerator().generateKeyTransportMessage(jid, axolotlMessage);
- mXmppConnectionService.sendMessagePacket(account, packet);
- } catch (IllegalArgumentException e) {
- throw new Error("Remote addresses are created from jid and should convert back to jid", e);
- }
- }
-
- public XmppAxolotlMessage.XmppAxolotlKeyTransportMessage processReceivingKeyTransportMessage(XmppAxolotlMessage message, final boolean postponePreKeyMessageHandling) {
- final XmppAxolotlMessage.XmppAxolotlKeyTransportMessage keyTransportMessage;
- final XmppAxolotlSession session = getReceivingSession(message);
- try {
- keyTransportMessage = message.getParameters(session, getOwnDeviceId());
- Integer preKeyId = session.getPreKeyIdAndReset();
- if (preKeyId != null) {
- postPreKeyMessageHandling(session, postponePreKeyMessageHandling);
- }
- } catch (CryptoFailedException e) {
- Log.d(Config.LOGTAG, "could not decrypt keyTransport message " + e.getMessage());
- return null;
- }
-
- if (session.isFresh() && keyTransportMessage != null) {
- putFreshSession(session);
- }
-
- return keyTransportMessage;
- }
-
- private ListenableFuture putFreshSession(XmppAxolotlSession session) {
- sessions.put(session);
- if (Config.X509_VERIFICATION) {
- if (session.getIdentityKey() != null) {
- return verifySessionWithPEP(session);
- } else {
- Log.e(Config.LOGTAG, account.getJid().asBareJid() + ": identity key was empty after reloading for x509 verification");
- }
- }
- return Futures.immediateFuture(session);
- }
-
- public enum FetchStatus {
- PENDING,
- SUCCESS,
- SUCCESS_VERIFIED,
- TIMEOUT,
- SUCCESS_TRUSTED,
- ERROR
- }
-
- public interface OnDeviceIdsFetched {
- void fetched(Jid jid, Set deviceIds);
- }
-
-
- public interface OnMultipleDeviceIdFetched {
- void fetched();
- }
-
- interface OnSessionBuildFromPep {
- void onSessionBuildSuccessful();
-
- void onSessionBuildFailed();
- }
-
- private static class AxolotlAddressMap {
- protected final Object MAP_LOCK = new Object();
- protected Map> map;
-
- public AxolotlAddressMap() {
- this.map = new HashMap<>();
- }
-
- public void put(SignalProtocolAddress address, T value) {
- synchronized (MAP_LOCK) {
- Map devices = map.get(address.getName());
- if (devices == null) {
- devices = new HashMap<>();
- map.put(address.getName(), devices);
- }
- devices.put(address.getDeviceId(), value);
- }
- }
-
- public T get(SignalProtocolAddress address) {
- synchronized (MAP_LOCK) {
- Map devices = map.get(address.getName());
- if (devices == null) {
- return null;
- }
- return devices.get(address.getDeviceId());
- }
- }
-
- public Map getAll(String name) {
- synchronized (MAP_LOCK) {
- Map devices = map.get(name);
- if (devices == null) {
- return new HashMap<>();
- }
- return devices;
- }
- }
-
- public boolean hasAny(SignalProtocolAddress address) {
- synchronized (MAP_LOCK) {
- Map devices = map.get(address.getName());
- return devices != null && !devices.isEmpty();
- }
- }
-
- public void clear() {
- map.clear();
- }
-
- }
-
- private static class SessionMap extends AxolotlAddressMap {
- private final XmppConnectionService xmppConnectionService;
- private final Account account;
-
- public SessionMap(XmppConnectionService service, SQLiteAxolotlStore store, Account account) {
- super();
- this.xmppConnectionService = service;
- this.account = account;
- this.fillMap(store);
- }
-
- public Set findCounterpartsForSourceId(Integer sid) {
- Set candidates = new HashSet<>();
- synchronized (MAP_LOCK) {
- for (Map.Entry> entry : map.entrySet()) {
- String key = entry.getKey();
- if (entry.getValue().containsKey(sid)) {
- candidates.add(Jid.of(key));
- }
- }
- }
- return candidates;
- }
-
- private void putDevicesForJid(String bareJid, List deviceIds, SQLiteAxolotlStore store) {
- for (Integer deviceId : deviceIds) {
- SignalProtocolAddress axolotlAddress = new SignalProtocolAddress(bareJid, deviceId);
- IdentityKey identityKey = store.loadSession(axolotlAddress).getSessionState().getRemoteIdentityKey();
- if (Config.X509_VERIFICATION) {
- X509Certificate certificate = store.getFingerprintCertificate(CryptoHelper.bytesToHex(identityKey.getPublicKey().serialize()));
- if (certificate != null) {
- Bundle information = CryptoHelper.extractCertificateInformation(certificate);
- try {
- final String cn = information.getString("subject_cn");
- final Jid jid = Jid.of(bareJid);
- Log.d(Config.LOGTAG, "setting common name for " + jid + " to " + cn);
- account.getRoster().getContact(jid).setCommonName(cn);
- } catch (final IllegalArgumentException ignored) {
- //ignored
- }
- }
- }
- this.put(axolotlAddress, new XmppAxolotlSession(account, store, axolotlAddress, identityKey));
- }
- }
-
- private void fillMap(SQLiteAxolotlStore store) {
- List deviceIds = store.getSubDeviceSessions(account.getJid().asBareJid().toString());
- putDevicesForJid(account.getJid().asBareJid().toString(), deviceIds, store);
- for (String address : store.getKnownAddresses()) {
- deviceIds = store.getSubDeviceSessions(address);
- putDevicesForJid(address, deviceIds, store);
- }
- }
-
- @Override
- public void put(SignalProtocolAddress address, XmppAxolotlSession value) {
- super.put(address, value);
- value.setNotFresh();
- }
-
- public void put(XmppAxolotlSession session) {
- this.put(session.getRemoteAddress(), session);
- }
- }
-
- private static class FetchStatusMap extends AxolotlAddressMap {
-
- public void clearErrorFor(Jid jid) {
- synchronized (MAP_LOCK) {
- Map devices = this.map.get(jid.asBareJid().toString());
- if (devices == null) {
- return;
- }
- for (Map.Entry entry : devices.entrySet()) {
- if (entry.getValue() == FetchStatus.ERROR) {
- Log.d(Config.LOGTAG, "resetting error for " + jid.asBareJid() + "(" + entry.getKey() + ")");
- entry.setValue(FetchStatus.TIMEOUT);
- }
- }
- }
- }
- }
-
- public static class OmemoVerifiedPayload {
- private final int deviceId;
- private final String fingerprint;
- private final T payload;
-
- private OmemoVerifiedPayload(OmemoVerification omemoVerification, T payload) {
- this.deviceId = omemoVerification.getDeviceId();
- this.fingerprint = omemoVerification.getFingerprint();
- this.payload = payload;
- }
-
- public int getDeviceId() {
- return deviceId;
- }
-
- public String getFingerprint() {
- return fingerprint;
- }
-
- public T getPayload() {
- return payload;
- }
- }
-
- public static class NotVerifiedException extends SecurityException {
-
- public NotVerifiedException(String message) {
- super(message);
- }
-
- }
-}
diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/BrokenSessionException.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/BrokenSessionException.java
deleted file mode 100644
index 60459295b..000000000
--- a/src/main/java/eu/siacs/conversations/crypto/axolotl/BrokenSessionException.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package eu.siacs.conversations.crypto.axolotl;
-
-import org.whispersystems.libsignal.SignalProtocolAddress;
-
-public class BrokenSessionException extends CryptoFailedException {
-
- private final SignalProtocolAddress signalProtocolAddress;
-
- public BrokenSessionException(SignalProtocolAddress address, Exception e) {
- super(e);
- this.signalProtocolAddress = address;
-
- }
-
- public SignalProtocolAddress getSignalProtocolAddress() {
- return signalProtocolAddress;
- }
-}
diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/CryptoFailedException.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/CryptoFailedException.java
deleted file mode 100644
index 67acc1678..000000000
--- a/src/main/java/eu/siacs/conversations/crypto/axolotl/CryptoFailedException.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package eu.siacs.conversations.crypto.axolotl;
-
-public class CryptoFailedException extends Exception {
-
- public CryptoFailedException(String msg) {
- super(msg);
- }
-
- public CryptoFailedException(String msg, Exception e) {
- super(msg, e);
- }
-
- public CryptoFailedException(Exception e){
- super(e);
- }
-}
diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/FingerprintStatus.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/FingerprintStatus.java
deleted file mode 100644
index 2f1856d09..000000000
--- a/src/main/java/eu/siacs/conversations/crypto/axolotl/FingerprintStatus.java
+++ /dev/null
@@ -1,184 +0,0 @@
-package eu.siacs.conversations.crypto.axolotl;
-
-import android.content.ContentValues;
-import android.database.Cursor;
-
-public class FingerprintStatus implements Comparable {
-
- private static final long DO_NOT_OVERWRITE = -1;
-
- private Trust trust = Trust.UNTRUSTED;
- private boolean active = false;
- private long lastActivation = DO_NOT_OVERWRITE;
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
-
- FingerprintStatus that = (FingerprintStatus) o;
-
- return active == that.active && trust == that.trust;
- }
-
- @Override
- public int hashCode() {
- int result = trust.hashCode();
- result = 31 * result + (active ? 1 : 0);
- return result;
- }
-
- private FingerprintStatus() {
-
-
- }
-
- public ContentValues toContentValues() {
- final ContentValues contentValues = new ContentValues();
- contentValues.put(SQLiteAxolotlStore.TRUST,trust.toString());
- contentValues.put(SQLiteAxolotlStore.ACTIVE,active ? 1 : 0);
- if (lastActivation != DO_NOT_OVERWRITE) {
- contentValues.put(SQLiteAxolotlStore.LAST_ACTIVATION,lastActivation);
- }
- return contentValues;
- }
-
- public static FingerprintStatus fromCursor(Cursor cursor) {
- final FingerprintStatus status = new FingerprintStatus();
- try {
- status.trust = Trust.valueOf(cursor.getString(cursor.getColumnIndex(SQLiteAxolotlStore.TRUST)));
- } catch(IllegalArgumentException e) {
- status.trust = Trust.UNTRUSTED;
- }
- status.active = cursor.getInt(cursor.getColumnIndex(SQLiteAxolotlStore.ACTIVE)) > 0;
- status.lastActivation = cursor.getLong(cursor.getColumnIndex(SQLiteAxolotlStore.LAST_ACTIVATION));
- return status;
- }
-
- public static FingerprintStatus createActiveUndecided() {
- final FingerprintStatus status = new FingerprintStatus();
- status.trust = Trust.UNDECIDED;
- status.active = true;
- status.lastActivation = System.currentTimeMillis();
- return status;
- }
-
- public static FingerprintStatus createActiveTrusted() {
- final FingerprintStatus status = new FingerprintStatus();
- status.trust = Trust.TRUSTED;
- status.active = true;
- status.lastActivation = System.currentTimeMillis();
- return status;
- }
-
- public static FingerprintStatus createActiveVerified(boolean x509) {
- final FingerprintStatus status = new FingerprintStatus();
- status.trust = x509 ? Trust.VERIFIED_X509 : Trust.VERIFIED;
- status.active = true;
- return status;
- }
-
- public static FingerprintStatus createActive(Boolean trusted) {
- return createActive(trusted != null && trusted);
- }
-
- public static FingerprintStatus createActive(boolean trusted) {
- final FingerprintStatus status = new FingerprintStatus();
- status.trust = trusted ? Trust.TRUSTED : Trust.UNTRUSTED;
- status.active = true;
- return status;
- }
-
- public boolean isTrustedAndActive() {
- return active && isTrusted();
- }
-
- public boolean isTrusted() {
- return trust == Trust.TRUSTED || isVerified();
- }
-
- public boolean isVerified() {
- return trust == Trust.VERIFIED || trust == Trust.VERIFIED_X509;
- }
-
- public boolean isCompromised() {
- return trust == Trust.COMPROMISED;
- }
-
- public boolean isActive() {
- return active;
- }
-
- public FingerprintStatus toActive() {
- FingerprintStatus status = new FingerprintStatus();
- status.trust = trust;
- if (!status.active) {
- status.lastActivation = System.currentTimeMillis();
- }
- status.active = true;
- return status;
- }
-
- public FingerprintStatus toInactive() {
- FingerprintStatus status = new FingerprintStatus();
- status.trust = trust;
- status.active = false;
- return status;
- }
-
- public Trust getTrust() {
- return trust;
- }
-
- public FingerprintStatus toVerified() {
- FingerprintStatus status = new FingerprintStatus();
- status.active = active;
- status.trust = Trust.VERIFIED;
- return status;
- }
-
- public FingerprintStatus toUntrusted() {
- FingerprintStatus status = new FingerprintStatus();
- status.active = active;
- status.trust = Trust.UNTRUSTED;
- return status;
- }
-
- public static FingerprintStatus createInactiveVerified() {
- final FingerprintStatus status = new FingerprintStatus();
- status.trust = Trust.VERIFIED;
- status.active = false;
- return status;
- }
-
- @Override
- public int compareTo(FingerprintStatus o) {
- if (active == o.active) {
- if (lastActivation > o.lastActivation) {
- return -1;
- } else if (lastActivation < o.lastActivation) {
- return 1;
- } else {
- return 0;
- }
- } else if (active){
- return -1;
- } else {
- return 1;
- }
- }
-
- public long getLastActivation() {
- return lastActivation;
- }
-
- public enum Trust {
- COMPROMISED,
- UNDECIDED,
- UNTRUSTED,
- TRUSTED,
- VERIFIED,
- VERIFIED_X509
- }
-
-}
diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/NoSessionsCreatedException.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/NoSessionsCreatedException.java
deleted file mode 100644
index 663b42b58..000000000
--- a/src/main/java/eu/siacs/conversations/crypto/axolotl/NoSessionsCreatedException.java
+++ /dev/null
@@ -1,4 +0,0 @@
-package eu.siacs.conversations.crypto.axolotl;
-
-public class NoSessionsCreatedException extends Throwable{
-}
diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/NotEncryptedForThisDeviceException.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/NotEncryptedForThisDeviceException.java
deleted file mode 100644
index f029f489a..000000000
--- a/src/main/java/eu/siacs/conversations/crypto/axolotl/NotEncryptedForThisDeviceException.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (c) 2018, Daniel Gultsch All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without modification,
- * are permitted provided that the following conditions are met:
- *
- * 1. Redistributions of source code must retain the above copyright notice, this
- * list of conditions and the following disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following disclaimer in the documentation and/or
- * other materials provided with the distribution.
- *
- * 3. Neither the name of the copyright holder nor the names of its contributors
- * may be used to endorse or promote products derived from this software without
- * specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
- * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
- * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-package eu.siacs.conversations.crypto.axolotl;
-
-
-public class NotEncryptedForThisDeviceException extends CryptoFailedException {
- public NotEncryptedForThisDeviceException() {
- super("Message was not encrypted for this device");
- }
-}
diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/OnMessageCreatedCallback.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/OnMessageCreatedCallback.java
deleted file mode 100644
index 3d40a4089..000000000
--- a/src/main/java/eu/siacs/conversations/crypto/axolotl/OnMessageCreatedCallback.java
+++ /dev/null
@@ -1,5 +0,0 @@
-package eu.siacs.conversations.crypto.axolotl;
-
-public interface OnMessageCreatedCallback {
- void run(XmppAxolotlMessage message);
-}
diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/OutdatedSenderException.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/OutdatedSenderException.java
deleted file mode 100644
index b684c678f..000000000
--- a/src/main/java/eu/siacs/conversations/crypto/axolotl/OutdatedSenderException.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package eu.siacs.conversations.crypto.axolotl;
-
-public class OutdatedSenderException extends CryptoFailedException {
-
- public OutdatedSenderException(final String msg) {
- super(msg);
- }
-}
diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/SQLiteAxolotlStore.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/SQLiteAxolotlStore.java
deleted file mode 100644
index 866449c18..000000000
--- a/src/main/java/eu/siacs/conversations/crypto/axolotl/SQLiteAxolotlStore.java
+++ /dev/null
@@ -1,477 +0,0 @@
-package eu.siacs.conversations.crypto.axolotl;
-
-import android.util.Log;
-import android.util.LruCache;
-
-import org.whispersystems.libsignal.IdentityKey;
-import org.whispersystems.libsignal.IdentityKeyPair;
-import org.whispersystems.libsignal.InvalidKeyIdException;
-import org.whispersystems.libsignal.SignalProtocolAddress;
-import org.whispersystems.libsignal.ecc.Curve;
-import org.whispersystems.libsignal.ecc.ECKeyPair;
-import org.whispersystems.libsignal.state.PreKeyRecord;
-import org.whispersystems.libsignal.state.SessionRecord;
-import org.whispersystems.libsignal.state.SignalProtocolStore;
-import org.whispersystems.libsignal.state.SignedPreKeyRecord;
-import org.whispersystems.libsignal.util.KeyHelper;
-
-import java.security.cert.X509Certificate;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-
-import eu.siacs.conversations.Config;
-import eu.siacs.conversations.entities.Account;
-import eu.siacs.conversations.services.XmppConnectionService;
-import eu.siacs.conversations.utils.CryptoHelper;
-
-public class SQLiteAxolotlStore implements SignalProtocolStore {
-
- public static final String PREKEY_TABLENAME = "prekeys";
- public static final String SIGNED_PREKEY_TABLENAME = "signed_prekeys";
- public static final String SESSION_TABLENAME = "sessions";
- public static final String IDENTITIES_TABLENAME = "identities";
- public static final String ACCOUNT = "account";
- public static final String DEVICE_ID = "device_id";
- public static final String ID = "id";
- public static final String KEY = "key";
- public static final String FINGERPRINT = "fingerprint";
- public static final String NAME = "name";
- public static final String TRUSTED = "trusted"; //no longer used
- public static final String TRUST = "trust";
- public static final String ACTIVE = "active";
- public static final String LAST_ACTIVATION = "last_activation";
- public static final String OWN = "ownkey";
- public static final String CERTIFICATE = "certificate";
-
- public static final String JSONKEY_REGISTRATION_ID = "axolotl_reg_id";
- public static final String JSONKEY_CURRENT_PREKEY_ID = "axolotl_cur_prekey_id";
-
- private static final int NUM_TRUSTS_TO_CACHE = 100;
-
- private final Account account;
- private final XmppConnectionService mXmppConnectionService;
-
- private IdentityKeyPair identityKeyPair;
- private int localRegistrationId;
- private int currentPreKeyId = 0;
-
- private final HashSet preKeysMarkedForRemoval = new HashSet<>();
-
- private final LruCache trustCache =
- new LruCache(NUM_TRUSTS_TO_CACHE) {
- @Override
- protected FingerprintStatus create(String fingerprint) {
- return mXmppConnectionService.databaseBackend.getFingerprintStatus(account, fingerprint);
- }
- };
-
- private static IdentityKeyPair generateIdentityKeyPair() {
- Log.i(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Generating axolotl IdentityKeyPair...");
- ECKeyPair identityKeyPairKeys = Curve.generateKeyPair();
- return new IdentityKeyPair(new IdentityKey(identityKeyPairKeys.getPublicKey()),
- identityKeyPairKeys.getPrivateKey());
- }
-
- private static int generateRegistrationId() {
- Log.i(Config.LOGTAG, AxolotlService.LOGPREFIX + " : " + "Generating axolotl registration ID...");
- return KeyHelper.generateRegistrationId(true);
- }
-
- public SQLiteAxolotlStore(Account account, XmppConnectionService service) {
- this.account = account;
- this.mXmppConnectionService = service;
- this.localRegistrationId = loadRegistrationId();
- this.currentPreKeyId = loadCurrentPreKeyId();
- }
-
- public int getCurrentPreKeyId() {
- return currentPreKeyId;
- }
-
- // --------------------------------------
- // IdentityKeyStore
- // --------------------------------------
-
- private IdentityKeyPair loadIdentityKeyPair() {
- synchronized (mXmppConnectionService) {
- IdentityKeyPair ownKey = mXmppConnectionService.databaseBackend.loadOwnIdentityKeyPair(account);
-
- if (ownKey != null) {
- return ownKey;
- } else {
- Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Could not retrieve own IdentityKeyPair");
- ownKey = generateIdentityKeyPair();
- mXmppConnectionService.databaseBackend.storeOwnIdentityKeyPair(account, ownKey);
- }
- return ownKey;
- }
- }
-
- private int loadRegistrationId() {
- return loadRegistrationId(false);
- }
-
- private int loadRegistrationId(boolean regenerate) {
- String regIdString = this.account.getKey(JSONKEY_REGISTRATION_ID);
- int reg_id;
- if (!regenerate && regIdString != null) {
- reg_id = Integer.valueOf(regIdString);
- } else {
- Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Could not retrieve axolotl registration id for account " + account.getJid());
- reg_id = generateRegistrationId();
- boolean success = this.account.setKey(JSONKEY_REGISTRATION_ID, Integer.toString(reg_id));
- if (success) {
- mXmppConnectionService.databaseBackend.updateAccount(account);
- } else {
- Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Failed to write new key to the database!");
- }
- }
- return reg_id;
- }
-
- private int loadCurrentPreKeyId() {
- String prekeyIdString = this.account.getKey(JSONKEY_CURRENT_PREKEY_ID);
- int prekey_id;
- if (prekeyIdString != null) {
- prekey_id = Integer.valueOf(prekeyIdString);
- } else {
- Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Could not retrieve current prekey id for account " + account.getJid());
- prekey_id = 0;
- }
- return prekey_id;
- }
-
- public void regenerate() {
- mXmppConnectionService.databaseBackend.wipeAxolotlDb(account);
- trustCache.evictAll();
- account.setKey(JSONKEY_CURRENT_PREKEY_ID, Integer.toString(0));
- identityKeyPair = loadIdentityKeyPair();
- localRegistrationId = loadRegistrationId(true);
- currentPreKeyId = 0;
- mXmppConnectionService.updateAccountUi();
- }
-
- /**
- * Get the local client's identity key pair.
- *
- * @return The local client's persistent identity key pair.
- */
- @Override
- public IdentityKeyPair getIdentityKeyPair() {
- if (identityKeyPair == null) {
- identityKeyPair = loadIdentityKeyPair();
- }
- return identityKeyPair;
- }
-
- /**
- * Return the local client's registration ID.
- *
- * Clients should maintain a registration ID, a random number
- * between 1 and 16380 that's generated once at install time.
- *
- * @return the local client's registration ID.
- */
- @Override
- public int getLocalRegistrationId() {
- return localRegistrationId;
- }
-
- /**
- * Save a remote client's identity key
- *
- * Store a remote client's identity key as trusted.
- *
- * @param address The address of the remote client.
- * @param identityKey The remote client's identity key.
- * @return true on success
- */
- @Override
- public boolean saveIdentity(SignalProtocolAddress address, IdentityKey identityKey) {
- if (!mXmppConnectionService.databaseBackend.loadIdentityKeys(account, address.getName()).contains(identityKey)) {
- String fingerprint = CryptoHelper.bytesToHex(identityKey.getPublicKey().serialize());
- FingerprintStatus status = getFingerprintStatus(fingerprint);
- if (status == null) {
- if (mXmppConnectionService.blindTrustBeforeVerification() && !account.getAxolotlService().hasVerifiedKeys(address.getName())) {
- Log.d(Config.LOGTAG,account.getJid().asBareJid()+": blindly trusted "+fingerprint+" of "+address.getName());
- status = FingerprintStatus.createActiveTrusted();
- } else {
- status = FingerprintStatus.createActiveUndecided();
- }
- } else {
- status = status.toActive();
- }
- mXmppConnectionService.databaseBackend.storeIdentityKey(account, address.getName(), identityKey, status);
- trustCache.remove(fingerprint);
- }
- return true;
- }
-
- /**
- * Verify a remote client's identity key.
- *
- * Determine whether a remote client's identity is trusted. Convention is
- * that the TextSecure protocol is 'trust on first use.' This means that
- * an identity key is considered 'trusted' if there is no entry for the recipient
- * in the local store, or if it matches the saved key for a recipient in the local
- * store. Only if it mismatches an entry in the local store is it considered
- * 'untrusted.'
- *
- * @param identityKey The identity key to verify.
- * @return true if trusted, false if untrusted.
- */
- @Override
- public boolean isTrustedIdentity(SignalProtocolAddress address, IdentityKey identityKey, Direction direction) {
- return true;
- }
-
- public FingerprintStatus getFingerprintStatus(String fingerprint) {
- return (fingerprint == null)? null : trustCache.get(fingerprint);
- }
-
- public void setFingerprintStatus(String fingerprint, FingerprintStatus status) {
- mXmppConnectionService.databaseBackend.setIdentityKeyTrust(account, fingerprint, status);
- trustCache.remove(fingerprint);
- }
-
- public void setFingerprintCertificate(String fingerprint, X509Certificate x509Certificate) {
- mXmppConnectionService.databaseBackend.setIdentityKeyCertificate(account, fingerprint, x509Certificate);
- }
-
- public X509Certificate getFingerprintCertificate(String fingerprint) {
- return mXmppConnectionService.databaseBackend.getIdentityKeyCertifcate(account, fingerprint);
- }
-
- public Set getContactKeysWithTrust(String bareJid, FingerprintStatus status) {
- return mXmppConnectionService.databaseBackend.loadIdentityKeys(account, bareJid, status);
- }
-
- public long getContactNumTrustedKeys(String bareJid) {
- return mXmppConnectionService.databaseBackend.numTrustedKeys(account, bareJid);
- }
-
- // --------------------------------------
- // SessionStore
- // --------------------------------------
-
- /**
- * Returns a copy of the {@link SessionRecord} corresponding to the recipientId + deviceId tuple,
- * or a new SessionRecord if one does not currently exist.
- *
- * It is important that implementations return a copy of the current durable information. The
- * returned SessionRecord may be modified, but those changes should not have an effect on the
- * durable session state (what is returned by subsequent calls to this method) without the
- * store method being called here first.
- *
- * @param address The name and device ID of the remote client.
- * @return a copy of the SessionRecord corresponding to the recipientId + deviceId tuple, or
- * a new SessionRecord if one does not currently exist.
- */
- @Override
- public SessionRecord loadSession(SignalProtocolAddress address) {
- SessionRecord session = mXmppConnectionService.databaseBackend.loadSession(this.account, address);
- return (session != null) ? session : new SessionRecord();
- }
-
- /**
- * Returns all known devices with active sessions for a recipient
- *
- * @param name the name of the client.
- * @return all known sub-devices with active sessions.
- */
- @Override
- public List getSubDeviceSessions(String name) {
- return mXmppConnectionService.databaseBackend.getSubDeviceSessions(account,
- new SignalProtocolAddress(name, 0));
- }
-
-
- public List getKnownAddresses() {
- return mXmppConnectionService.databaseBackend.getKnownSignalAddresses(account);
- }
- /**
- * Commit to storage the {@link SessionRecord} for a given recipientId + deviceId tuple.
- *
- * @param address the address of the remote client.
- * @param record the current SessionRecord for the remote client.
- */
- @Override
- public void storeSession(SignalProtocolAddress address, SessionRecord record) {
- mXmppConnectionService.databaseBackend.storeSession(account, address, record);
- }
-
- /**
- * Determine whether there is a committed {@link SessionRecord} for a recipientId + deviceId tuple.
- *
- * @param address the address of the remote client.
- * @return true if a {@link SessionRecord} exists, false otherwise.
- */
- @Override
- public boolean containsSession(SignalProtocolAddress address) {
- return mXmppConnectionService.databaseBackend.containsSession(account, address);
- }
-
- /**
- * Remove a {@link SessionRecord} for a recipientId + deviceId tuple.
- *
- * @param address the address of the remote client.
- */
- @Override
- public void deleteSession(SignalProtocolAddress address) {
- mXmppConnectionService.databaseBackend.deleteSession(account, address);
- }
-
- /**
- * Remove the {@link SessionRecord}s corresponding to all devices of a recipientId.
- *
- * @param name the name of the remote client.
- */
- @Override
- public void deleteAllSessions(String name) {
- SignalProtocolAddress address = new SignalProtocolAddress(name, 0);
- mXmppConnectionService.databaseBackend.deleteAllSessions(account,
- address);
- }
-
- // --------------------------------------
- // PreKeyStore
- // --------------------------------------
-
- /**
- * Load a local PreKeyRecord.
- *
- * @param preKeyId the ID of the local PreKeyRecord.
- * @return the corresponding PreKeyRecord.
- * @throws InvalidKeyIdException when there is no corresponding PreKeyRecord.
- */
- @Override
- public PreKeyRecord loadPreKey(int preKeyId) throws InvalidKeyIdException {
- PreKeyRecord record = mXmppConnectionService.databaseBackend.loadPreKey(account, preKeyId);
- if (record == null) {
- throw new InvalidKeyIdException("No such PreKeyRecord: " + preKeyId);
- }
- return record;
- }
-
- /**
- * Store a local PreKeyRecord.
- *
- * @param preKeyId the ID of the PreKeyRecord to store.
- * @param record the PreKeyRecord.
- */
- @Override
- public void storePreKey(int preKeyId, PreKeyRecord record) {
- mXmppConnectionService.databaseBackend.storePreKey(account, record);
- currentPreKeyId = preKeyId;
- boolean success = this.account.setKey(JSONKEY_CURRENT_PREKEY_ID, Integer.toString(preKeyId));
- if (success) {
- mXmppConnectionService.databaseBackend.updateAccount(account);
- } else {
- Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Failed to write new prekey id to the database!");
- }
- }
-
- /**
- * @param preKeyId A PreKeyRecord ID.
- * @return true if the store has a record for the preKeyId, otherwise false.
- */
- @Override
- public boolean containsPreKey(int preKeyId) {
- return mXmppConnectionService.databaseBackend.containsPreKey(account, preKeyId);
- }
-
- /**
- * Delete a PreKeyRecord from local storage.
- *
- * @param preKeyId The ID of the PreKeyRecord to remove.
- */
- @Override
- public void removePreKey(int preKeyId) {
- Log.d(Config.LOGTAG,"mark prekey for removal "+preKeyId);
- synchronized (preKeysMarkedForRemoval) {
- preKeysMarkedForRemoval.add(preKeyId);
- }
- }
-
-
- public boolean flushPreKeys() {
- Log.d(Config.LOGTAG,"flushing pre keys");
- int count = 0;
- synchronized (preKeysMarkedForRemoval) {
- for(Integer preKeyId : preKeysMarkedForRemoval) {
- count += mXmppConnectionService.databaseBackend.deletePreKey(account, preKeyId);
- }
- preKeysMarkedForRemoval.clear();
- }
- return count > 0;
- }
-
- // --------------------------------------
- // SignedPreKeyStore
- // --------------------------------------
-
- /**
- * Load a local SignedPreKeyRecord.
- *
- * @param signedPreKeyId the ID of the local SignedPreKeyRecord.
- * @return the corresponding SignedPreKeyRecord.
- * @throws InvalidKeyIdException when there is no corresponding SignedPreKeyRecord.
- */
- @Override
- public SignedPreKeyRecord loadSignedPreKey(int signedPreKeyId) throws InvalidKeyIdException {
- SignedPreKeyRecord record = mXmppConnectionService.databaseBackend.loadSignedPreKey(account, signedPreKeyId);
- if (record == null) {
- throw new InvalidKeyIdException("No such SignedPreKeyRecord: " + signedPreKeyId);
- }
- return record;
- }
-
- /**
- * Load all local SignedPreKeyRecords.
- *
- * @return All stored SignedPreKeyRecords.
- */
- @Override
- public List loadSignedPreKeys() {
- return mXmppConnectionService.databaseBackend.loadSignedPreKeys(account);
- }
-
- public int getSignedPreKeysCount() {
- return mXmppConnectionService.databaseBackend.getSignedPreKeysCount(account);
- }
-
- /**
- * Store a local SignedPreKeyRecord.
- *
- * @param signedPreKeyId the ID of the SignedPreKeyRecord to store.
- * @param record the SignedPreKeyRecord.
- */
- @Override
- public void storeSignedPreKey(int signedPreKeyId, SignedPreKeyRecord record) {
- mXmppConnectionService.databaseBackend.storeSignedPreKey(account, record);
- }
-
- /**
- * @param signedPreKeyId A SignedPreKeyRecord ID.
- * @return true if the store has a record for the signedPreKeyId, otherwise false.
- */
- @Override
- public boolean containsSignedPreKey(int signedPreKeyId) {
- return mXmppConnectionService.databaseBackend.containsSignedPreKey(account, signedPreKeyId);
- }
-
- /**
- * Delete a SignedPreKeyRecord from local storage.
- *
- * @param signedPreKeyId The ID of the SignedPreKeyRecord to remove.
- */
- @Override
- public void removeSignedPreKey(int signedPreKeyId) {
- mXmppConnectionService.databaseBackend.deleteSignedPreKey(account, signedPreKeyId);
- }
-
- public void preVerifyFingerprint(Account account, String name, String fingerprint) {
- mXmppConnectionService.databaseBackend.storePreVerification(account,name,fingerprint,FingerprintStatus.createInactiveVerified());
- }
-}
diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java
deleted file mode 100644
index bba24d90b..000000000
--- a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlMessage.java
+++ /dev/null
@@ -1,317 +0,0 @@
-package eu.siacs.conversations.crypto.axolotl;
-
-import android.util.Base64;
-import android.util.Log;
-
-import java.security.InvalidAlgorithmParameterException;
-import java.security.InvalidKeyException;
-import java.security.NoSuchAlgorithmException;
-import java.security.NoSuchProviderException;
-import java.security.SecureRandom;
-import java.util.ArrayList;
-import java.util.List;
-
-import javax.crypto.BadPaddingException;
-import javax.crypto.Cipher;
-import javax.crypto.IllegalBlockSizeException;
-import javax.crypto.KeyGenerator;
-import javax.crypto.NoSuchPaddingException;
-import javax.crypto.SecretKey;
-import javax.crypto.spec.IvParameterSpec;
-import javax.crypto.spec.SecretKeySpec;
-
-import eu.siacs.conversations.Config;
-import eu.siacs.conversations.utils.Compatibility;
-import eu.siacs.conversations.xml.Element;
-import eu.siacs.conversations.xmpp.Jid;
-
-public class XmppAxolotlMessage {
- public static final String CONTAINERTAG = "encrypted";
- private static final String HEADER = "header";
- private static final String SOURCEID = "sid";
- private static final String KEYTAG = "key";
- private static final String REMOTEID = "rid";
- private static final String IVTAG = "iv";
- private static final String PAYLOAD = "payload";
-
- private static final String KEYTYPE = "AES";
- private static final String CIPHERMODE = "AES/GCM/NoPadding";
- private static final String PROVIDER = "BC";
- private final List keys;
- private final Jid from;
- private final int sourceDeviceId;
- private byte[] innerKey;
- private byte[] ciphertext = null;
- private byte[] authtagPlusInnerKey = null;
- private byte[] iv = null;
-
- private XmppAxolotlMessage(final Element axolotlMessage, final Jid from) throws IllegalArgumentException {
- this.from = from;
- Element header = axolotlMessage.findChild(HEADER);
- try {
- this.sourceDeviceId = Integer.parseInt(header.getAttribute(SOURCEID));
- } catch (NumberFormatException e) {
- throw new IllegalArgumentException("invalid source id");
- }
- List keyElements = header.getChildren();
- this.keys = new ArrayList<>();
- for (Element keyElement : keyElements) {
- switch (keyElement.getName()) {
- case KEYTAG:
- try {
- int recipientId = Integer.parseInt(keyElement.getAttribute(REMOTEID));
- byte[] key = Base64.decode(keyElement.getContent().trim(), Base64.DEFAULT);
- boolean isPreKey = keyElement.getAttributeAsBoolean("prekey");
- this.keys.add(new XmppAxolotlSession.AxolotlKey(recipientId, key, isPreKey));
- } catch (NumberFormatException e) {
- throw new IllegalArgumentException("invalid remote id");
- }
- break;
- case IVTAG:
- if (this.iv != null) {
- throw new IllegalArgumentException("Duplicate iv entry");
- }
- iv = Base64.decode(keyElement.getContent().trim(), Base64.DEFAULT);
- break;
- default:
- Log.w(Config.LOGTAG, "Unexpected element in header: " + keyElement.toString());
- break;
- }
- }
- final Element payloadElement = axolotlMessage.findChildEnsureSingle(PAYLOAD, AxolotlService.PEP_PREFIX);
- if (payloadElement != null) {
- ciphertext = Base64.decode(payloadElement.getContent().trim(), Base64.DEFAULT);
- }
- }
-
- XmppAxolotlMessage(Jid from, int sourceDeviceId) {
- this.from = from;
- this.sourceDeviceId = sourceDeviceId;
- this.keys = new ArrayList<>();
- this.iv = generateIv();
- this.innerKey = generateKey();
- }
-
- public static int parseSourceId(final Element axolotlMessage) throws IllegalArgumentException {
- final Element header = axolotlMessage.findChild(HEADER);
- if (header == null) {
- throw new IllegalArgumentException("No header found");
- }
- try {
- return Integer.parseInt(header.getAttribute(SOURCEID));
- } catch (NumberFormatException e) {
- throw new IllegalArgumentException("invalid source id");
- }
- }
-
- public static XmppAxolotlMessage fromElement(Element element, Jid from) {
- return new XmppAxolotlMessage(element, from);
- }
-
- private static byte[] generateKey() {
- try {
- KeyGenerator generator = KeyGenerator.getInstance(KEYTYPE);
- generator.init(128);
- return generator.generateKey().getEncoded();
- } catch (NoSuchAlgorithmException e) {
- throw new IllegalStateException(e);
- }
- }
-
- private static byte[] generateIv() {
- final SecureRandom random = new SecureRandom();
- final byte[] iv = new byte[12];
- random.nextBytes(iv);
- return iv;
- }
-
- private static byte[] getPaddedBytes(String plaintext) {
- int plainLength = plaintext.getBytes().length;
- int pad = Math.max(64, (plainLength / 32 + 1) * 32) - plainLength;
- SecureRandom random = new SecureRandom();
- int left = random.nextInt(pad);
- int right = pad - left;
- StringBuilder builder = new StringBuilder(plaintext);
- for (int i = 0; i < left; ++i) {
- builder.insert(0, random.nextBoolean() ? "\t" : " ");
- }
- for (int i = 0; i < right; ++i) {
- builder.append(random.nextBoolean() ? "\t" : " ");
- }
- return builder.toString().getBytes();
- }
-
- public boolean hasPayload() {
- return ciphertext != null;
- }
-
- void encrypt(final String plaintext) throws CryptoFailedException {
- try {
- SecretKey secretKey = new SecretKeySpec(innerKey, KEYTYPE);
- IvParameterSpec ivSpec = new IvParameterSpec(iv);
- Cipher cipher = Compatibility.twentyEight() ? Cipher.getInstance(CIPHERMODE) : Cipher.getInstance(CIPHERMODE, PROVIDER);
- cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);
- this.ciphertext = cipher.doFinal(Config.OMEMO_PADDING ? getPaddedBytes(plaintext) : plaintext.getBytes());
- if (Config.PUT_AUTH_TAG_INTO_KEY && this.ciphertext != null) {
- this.authtagPlusInnerKey = new byte[16 + 16];
- byte[] ciphertext = new byte[this.ciphertext.length - 16];
- System.arraycopy(this.ciphertext, 0, ciphertext, 0, ciphertext.length);
- System.arraycopy(this.ciphertext, ciphertext.length, authtagPlusInnerKey, 16, 16);
- System.arraycopy(this.innerKey, 0, authtagPlusInnerKey, 0, this.innerKey.length);
- this.ciphertext = ciphertext;
- }
- } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
- | IllegalBlockSizeException | BadPaddingException | NoSuchProviderException
- | InvalidAlgorithmParameterException e) {
- throw new CryptoFailedException(e);
- }
- }
-
- public Jid getFrom() {
- return this.from;
- }
-
- int getSenderDeviceId() {
- return sourceDeviceId;
- }
-
- void addDevice(XmppAxolotlSession session) {
- addDevice(session, false);
- }
-
- void addDevice(XmppAxolotlSession session, boolean ignoreSessionTrust) {
- XmppAxolotlSession.AxolotlKey key;
- if (authtagPlusInnerKey != null) {
- key = session.processSending(authtagPlusInnerKey, ignoreSessionTrust);
- } else {
- key = session.processSending(innerKey, ignoreSessionTrust);
- }
- if (key != null) {
- keys.add(key);
- }
- }
-
- public byte[] getInnerKey() {
- return innerKey;
- }
-
- public byte[] getIV() {
- return this.iv;
- }
-
- public Element toElement() {
- Element encryptionElement = new Element(CONTAINERTAG, AxolotlService.PEP_PREFIX);
- Element headerElement = encryptionElement.addChild(HEADER);
- headerElement.setAttribute(SOURCEID, sourceDeviceId);
- for (XmppAxolotlSession.AxolotlKey key : keys) {
- Element keyElement = new Element(KEYTAG);
- keyElement.setAttribute(REMOTEID, key.deviceId);
- if (key.prekey) {
- keyElement.setAttribute("prekey", "true");
- }
- keyElement.setContent(Base64.encodeToString(key.key, Base64.NO_WRAP));
- headerElement.addChild(keyElement);
- }
- headerElement.addChild(IVTAG).setContent(Base64.encodeToString(iv, Base64.NO_WRAP));
- if (ciphertext != null) {
- Element payload = encryptionElement.addChild(PAYLOAD);
- payload.setContent(Base64.encodeToString(ciphertext, Base64.NO_WRAP));
- }
- return encryptionElement;
- }
-
- private byte[] unpackKey(XmppAxolotlSession session, Integer sourceDeviceId) throws CryptoFailedException {
- ArrayList possibleKeys = new ArrayList<>();
- for (XmppAxolotlSession.AxolotlKey key : keys) {
- if (key.deviceId == sourceDeviceId) {
- possibleKeys.add(key);
- }
- }
- if (possibleKeys.size() == 0) {
- throw new NotEncryptedForThisDeviceException();
- }
- return session.processReceiving(possibleKeys);
- }
-
- XmppAxolotlKeyTransportMessage getParameters(XmppAxolotlSession session, Integer sourceDeviceId) throws CryptoFailedException {
- return new XmppAxolotlKeyTransportMessage(session.getFingerprint(), unpackKey(session, sourceDeviceId), getIV());
- }
-
- public XmppAxolotlPlaintextMessage decrypt(XmppAxolotlSession session, Integer sourceDeviceId) throws CryptoFailedException {
- XmppAxolotlPlaintextMessage plaintextMessage = null;
- byte[] key = unpackKey(session, sourceDeviceId);
- if (key != null) {
- try {
- if (key.length < 32) {
- throw new OutdatedSenderException("Key did not contain auth tag. Sender needs to update their OMEMO client");
- }
- final int authTagLength = key.length - 16;
- byte[] newCipherText = new byte[key.length - 16 + ciphertext.length];
- byte[] newKey = new byte[16];
- System.arraycopy(ciphertext, 0, newCipherText, 0, ciphertext.length);
- System.arraycopy(key, 16, newCipherText, ciphertext.length, authTagLength);
- System.arraycopy(key, 0, newKey, 0, newKey.length);
- ciphertext = newCipherText;
- key = newKey;
-
- final Cipher cipher = Compatibility.twentyEight() ? Cipher.getInstance(CIPHERMODE) : Cipher.getInstance(CIPHERMODE, PROVIDER);
- SecretKeySpec keySpec = new SecretKeySpec(key, KEYTYPE);
- IvParameterSpec ivSpec = new IvParameterSpec(iv);
-
- cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
-
- String plaintext = new String(cipher.doFinal(ciphertext));
- plaintextMessage = new XmppAxolotlPlaintextMessage(Config.OMEMO_PADDING ? plaintext.trim() : plaintext, session.getFingerprint());
-
- } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException
- | InvalidAlgorithmParameterException | IllegalBlockSizeException
- | BadPaddingException | NoSuchProviderException e) {
- throw new CryptoFailedException(e);
- }
- }
- return plaintextMessage;
- }
-
- public static class XmppAxolotlPlaintextMessage {
- private final String plaintext;
- private final String fingerprint;
-
- XmppAxolotlPlaintextMessage(String plaintext, String fingerprint) {
- this.plaintext = plaintext;
- this.fingerprint = fingerprint;
- }
-
- public String getPlaintext() {
- return plaintext;
- }
-
-
- public String getFingerprint() {
- return fingerprint;
- }
- }
-
- public static class XmppAxolotlKeyTransportMessage {
- private final String fingerprint;
- private final byte[] key;
- private final byte[] iv;
-
- XmppAxolotlKeyTransportMessage(String fingerprint, byte[] key, byte[] iv) {
- this.fingerprint = fingerprint;
- this.key = key;
- this.iv = iv;
- }
-
- public String getFingerprint() {
- return fingerprint;
- }
-
- public byte[] getKey() {
- return key;
- }
-
- public byte[] getIv() {
- return iv;
- }
- }
-}
diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java
deleted file mode 100644
index 20af42fe4..000000000
--- a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java
+++ /dev/null
@@ -1,186 +0,0 @@
-package eu.siacs.conversations.crypto.axolotl;
-
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import org.whispersystems.libsignal.DuplicateMessageException;
-import org.whispersystems.libsignal.IdentityKey;
-import org.whispersystems.libsignal.InvalidKeyException;
-import org.whispersystems.libsignal.InvalidKeyIdException;
-import org.whispersystems.libsignal.InvalidMessageException;
-import org.whispersystems.libsignal.InvalidVersionException;
-import org.whispersystems.libsignal.LegacyMessageException;
-import org.whispersystems.libsignal.NoSessionException;
-import org.whispersystems.libsignal.SessionCipher;
-import org.whispersystems.libsignal.SignalProtocolAddress;
-import org.whispersystems.libsignal.UntrustedIdentityException;
-import org.whispersystems.libsignal.protocol.CiphertextMessage;
-import org.whispersystems.libsignal.protocol.PreKeySignalMessage;
-import org.whispersystems.libsignal.protocol.SignalMessage;
-import org.whispersystems.libsignal.util.guava.Optional;
-
-import java.util.Iterator;
-import java.util.List;
-
-import eu.siacs.conversations.Config;
-import eu.siacs.conversations.entities.Account;
-import eu.siacs.conversations.utils.CryptoHelper;
-
-public class XmppAxolotlSession implements Comparable {
- private final SessionCipher cipher;
- private final SQLiteAxolotlStore sqLiteAxolotlStore;
- private final SignalProtocolAddress remoteAddress;
- private final Account account;
- private IdentityKey identityKey;
- private Integer preKeyId = null;
- private boolean fresh = true;
-
- public XmppAxolotlSession(Account account, SQLiteAxolotlStore store, SignalProtocolAddress remoteAddress, IdentityKey identityKey) {
- this(account, store, remoteAddress);
- this.identityKey = identityKey;
- }
-
- public XmppAxolotlSession(Account account, SQLiteAxolotlStore store, SignalProtocolAddress remoteAddress) {
- this.cipher = new SessionCipher(store, remoteAddress);
- this.remoteAddress = remoteAddress;
- this.sqLiteAxolotlStore = store;
- this.account = account;
- }
-
- public Integer getPreKeyIdAndReset() {
- final Integer preKeyId = this.preKeyId;
- this.preKeyId = null;
- return preKeyId;
- }
-
- public String getFingerprint() {
- return identityKey == null ? null : CryptoHelper.bytesToHex(identityKey.getPublicKey().serialize());
- }
-
- public IdentityKey getIdentityKey() {
- return identityKey;
- }
-
- public SignalProtocolAddress getRemoteAddress() {
- return remoteAddress;
- }
-
- public boolean isFresh() {
- return fresh;
- }
-
- public void setNotFresh() {
- this.fresh = false;
- }
-
- protected void setTrust(FingerprintStatus status) {
- sqLiteAxolotlStore.setFingerprintStatus(getFingerprint(), status);
- }
-
- public FingerprintStatus getTrust() {
- FingerprintStatus status = sqLiteAxolotlStore.getFingerprintStatus(getFingerprint());
- return (status == null) ? FingerprintStatus.createActiveUndecided() : status;
- }
-
- @Nullable
- byte[] processReceiving(List possibleKeys) throws CryptoFailedException {
- byte[] plaintext = null;
- FingerprintStatus status = getTrust();
- if (!status.isCompromised()) {
- Iterator iterator = possibleKeys.iterator();
- while (iterator.hasNext()) {
- AxolotlKey encryptedKey = iterator.next();
- try {
- if (encryptedKey.prekey) {
- PreKeySignalMessage preKeySignalMessage = new PreKeySignalMessage(encryptedKey.key);
- Optional optionalPreKeyId = preKeySignalMessage.getPreKeyId();
- IdentityKey identityKey = preKeySignalMessage.getIdentityKey();
- if (!optionalPreKeyId.isPresent()) {
- if (iterator.hasNext()) {
- continue;
- }
- throw new CryptoFailedException("PreKeyWhisperMessage did not contain a PreKeyId");
- }
- preKeyId = optionalPreKeyId.get();
- if (this.identityKey != null && !this.identityKey.equals(identityKey)) {
- if (iterator.hasNext()) {
- continue;
- }
- throw new CryptoFailedException("Received PreKeyWhisperMessage but preexisting identity key changed.");
- }
- this.identityKey = identityKey;
- plaintext = cipher.decrypt(preKeySignalMessage);
- } else {
- SignalMessage signalMessage = new SignalMessage(encryptedKey.key);
- try {
- plaintext = cipher.decrypt(signalMessage);
- } catch (InvalidMessageException | NoSessionException e) {
- if (iterator.hasNext()) {
- Log.w(Config.LOGTAG,account.getJid().asBareJid()+": ignoring crypto exception because possible keys left to try",e);
- continue;
- }
- throw new BrokenSessionException(this.remoteAddress, e);
- }
- preKeyId = null; //better safe than sorry because we use that to do special after prekey handling
- }
- } catch (InvalidVersionException | InvalidKeyException | LegacyMessageException | InvalidMessageException | DuplicateMessageException | InvalidKeyIdException | UntrustedIdentityException e) {
- if (iterator.hasNext()) {
- Log.w(Config.LOGTAG,account.getJid().asBareJid()+": ignoring crypto exception because possible keys left to try",e);
- continue;
- }
- throw new CryptoFailedException("Error decrypting SignalMessage", e);
- }
- if (iterator.hasNext()) {
- break;
- }
- }
- if (!status.isActive()) {
- setTrust(status.toActive());
- //TODO: also (re)add to device list?
- }
- } else {
- throw new CryptoFailedException("not encrypting omemo message from fingerprint "+getFingerprint()+" because it was marked as compromised");
- }
- return plaintext;
- }
-
- @Nullable
- public AxolotlKey processSending(@NonNull byte[] outgoingMessage, boolean ignoreSessionTrust) {
- FingerprintStatus status = getTrust();
- if (ignoreSessionTrust || status.isTrustedAndActive()) {
- try {
- CiphertextMessage ciphertextMessage = cipher.encrypt(outgoingMessage);
- return new AxolotlKey(getRemoteAddress().getDeviceId(), ciphertextMessage.serialize(),ciphertextMessage.getType() == CiphertextMessage.PREKEY_TYPE);
- } catch (UntrustedIdentityException e) {
- return null;
- }
- } else {
- return null;
- }
- }
-
- public Account getAccount() {
- return account;
- }
-
- @Override
- public int compareTo(XmppAxolotlSession o) {
- return getTrust().compareTo(o.getTrust());
- }
-
- public static class AxolotlKey {
-
-
- public final byte[] key;
- public final boolean prekey;
- public final int deviceId;
-
- public AxolotlKey(int deviceId, byte[] key, boolean prekey) {
- this.deviceId = deviceId;
- this.key = key;
- this.prekey = prekey;
- }
- }
-}
diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/Anonymous.java b/src/main/java/eu/siacs/conversations/crypto/sasl/Anonymous.java
deleted file mode 100644
index 6fc4b11ce..000000000
--- a/src/main/java/eu/siacs/conversations/crypto/sasl/Anonymous.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package eu.siacs.conversations.crypto.sasl;
-
-import javax.net.ssl.SSLSocket;
-
-import eu.siacs.conversations.entities.Account;
-
-public class Anonymous extends SaslMechanism {
-
- public static final String MECHANISM = "ANONYMOUS";
-
- public Anonymous(final Account account) {
- super(account);
- }
-
- @Override
- public int getPriority() {
- return 0;
- }
-
- @Override
- public String getMechanism() {
- return MECHANISM;
- }
-
- @Override
- public String getClientFirstMessage(final SSLSocket sslSocket) {
- return "";
- }
-}
diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/ChannelBinding.java b/src/main/java/eu/siacs/conversations/crypto/sasl/ChannelBinding.java
deleted file mode 100644
index 216f3d7f8..000000000
--- a/src/main/java/eu/siacs/conversations/crypto/sasl/ChannelBinding.java
+++ /dev/null
@@ -1,120 +0,0 @@
-package eu.siacs.conversations.crypto.sasl;
-
-import android.util.Log;
-
-import com.google.common.base.CaseFormat;
-import com.google.common.base.Preconditions;
-import com.google.common.base.Predicates;
-import com.google.common.base.Strings;
-import com.google.common.collect.BiMap;
-import com.google.common.collect.Collections2;
-import com.google.common.collect.ImmutableBiMap;
-
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-
-import eu.siacs.conversations.Config;
-import eu.siacs.conversations.utils.SSLSockets;
-import eu.siacs.conversations.xml.Element;
-import eu.siacs.conversations.xml.Namespace;
-
-public enum ChannelBinding {
- NONE,
- TLS_EXPORTER,
- TLS_SERVER_END_POINT,
- TLS_UNIQUE;
-
- public static final BiMap SHORT_NAMES;
-
- static {
- final ImmutableBiMap.Builder builder = ImmutableBiMap.builder();
- for (final ChannelBinding cb : values()) {
- builder.put(cb, shortName(cb));
- }
- SHORT_NAMES = builder.build();
- }
-
- public static Collection of(final Element channelBinding) {
- Preconditions.checkArgument(
- channelBinding == null
- || ("sasl-channel-binding".equals(channelBinding.getName())
- && Namespace.CHANNEL_BINDING.equals(channelBinding.getNamespace())),
- "pass null or a valid channel binding stream feature");
- return Collections2.filter(
- Collections2.transform(
- Collections2.filter(
- channelBinding == null
- ? Collections.emptyList()
- : channelBinding.getChildren(),
- c -> c != null && "channel-binding".equals(c.getName())),
- c -> c == null ? null : ChannelBinding.of(c.getAttribute("type"))),
- Predicates.notNull());
- }
-
- private static ChannelBinding of(final String type) {
- if (type == null) {
- return null;
- }
- try {
- return valueOf(
- CaseFormat.LOWER_HYPHEN.converterTo(CaseFormat.UPPER_UNDERSCORE).convert(type));
- } catch (final IllegalArgumentException e) {
- Log.d(Config.LOGTAG, type + " is not a known channel binding");
- return null;
- }
- }
-
- public static ChannelBinding get(final String name) {
- if (Strings.isNullOrEmpty(name)) {
- return NONE;
- }
- try {
- return valueOf(name);
- } catch (final IllegalArgumentException e) {
- return NONE;
- }
- }
-
- public static ChannelBinding best(
- final Collection bindings, final SSLSockets.Version sslVersion) {
- if (sslVersion == SSLSockets.Version.NONE) {
- return NONE;
- }
- if (bindings.contains(TLS_EXPORTER) && sslVersion == SSLSockets.Version.TLS_1_3) {
- return TLS_EXPORTER;
- } else if (bindings.contains(TLS_UNIQUE)
- && Arrays.asList(
- SSLSockets.Version.TLS_1_0,
- SSLSockets.Version.TLS_1_1,
- SSLSockets.Version.TLS_1_2)
- .contains(sslVersion)) {
- return TLS_UNIQUE;
- } else if (bindings.contains(TLS_SERVER_END_POINT)) {
- return TLS_SERVER_END_POINT;
- } else {
- return NONE;
- }
- }
-
- public static boolean isAvailable(
- final ChannelBinding channelBinding, final SSLSockets.Version sslVersion) {
- return ChannelBinding.best(Collections.singleton(channelBinding), sslVersion)
- == channelBinding;
- }
-
- private static String shortName(final ChannelBinding channelBinding) {
- switch (channelBinding) {
- case TLS_UNIQUE:
- return "UNIQ";
- case TLS_EXPORTER:
- return "EXPR";
- case TLS_SERVER_END_POINT:
- return "ENDP";
- case NONE:
- return "NONE";
- default:
- throw new AssertionError("Missing short name for " + channelBinding);
- }
- }
-}
diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/ChannelBindingMechanism.java b/src/main/java/eu/siacs/conversations/crypto/sasl/ChannelBindingMechanism.java
deleted file mode 100644
index b94210a60..000000000
--- a/src/main/java/eu/siacs/conversations/crypto/sasl/ChannelBindingMechanism.java
+++ /dev/null
@@ -1,100 +0,0 @@
-package eu.siacs.conversations.crypto.sasl;
-
-import org.bouncycastle.jcajce.provider.digest.SHA256;
-import org.conscrypt.Conscrypt;
-
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-import java.security.cert.Certificate;
-import java.security.cert.CertificateEncodingException;
-import java.security.cert.X509Certificate;
-
-import javax.net.ssl.SSLException;
-import javax.net.ssl.SSLPeerUnverifiedException;
-import javax.net.ssl.SSLSession;
-import javax.net.ssl.SSLSocket;
-
-public interface ChannelBindingMechanism {
-
- String EXPORTER_LABEL = "EXPORTER-Channel-Binding";
-
- ChannelBinding getChannelBinding();
-
- static byte[] getChannelBindingData(final SSLSocket sslSocket, final ChannelBinding channelBinding)
- throws SaslMechanism.AuthenticationException {
- if (sslSocket == null) {
- throw new SaslMechanism.AuthenticationException("Channel binding attempt on non secure socket");
- }
- if (channelBinding == ChannelBinding.TLS_EXPORTER) {
- final byte[] keyingMaterial;
- try {
- keyingMaterial =
- Conscrypt.exportKeyingMaterial(sslSocket, EXPORTER_LABEL, new byte[0], 32);
- } catch (final SSLException e) {
- throw new SaslMechanism.AuthenticationException("Could not export keying material");
- }
- if (keyingMaterial == null) {
- throw new SaslMechanism.AuthenticationException(
- "Could not export keying material. Socket not ready");
- }
- return keyingMaterial;
- } else if (channelBinding == ChannelBinding.TLS_UNIQUE) {
- final byte[] unique = Conscrypt.getTlsUnique(sslSocket);
- if (unique == null) {
- throw new SaslMechanism.AuthenticationException(
- "Could not retrieve tls unique. Socket not ready");
- }
- return unique;
- } else if (channelBinding == ChannelBinding.TLS_SERVER_END_POINT) {
- return getServerEndPointChannelBinding(sslSocket.getSession());
- } else {
- throw new SaslMechanism.AuthenticationException(
- String.format("%s is not a valid channel binding", channelBinding));
- }
- }
-
- static byte[] getServerEndPointChannelBinding(final SSLSession session)
- throws SaslMechanism.AuthenticationException {
- final Certificate[] certificates;
- try {
- certificates = session.getPeerCertificates();
- } catch (final SSLPeerUnverifiedException e) {
- throw new SaslMechanism.AuthenticationException("Could not verify peer certificates");
- }
- if (certificates == null || certificates.length == 0) {
- throw new SaslMechanism.AuthenticationException("Could not retrieve peer certificate");
- }
- final X509Certificate certificate;
- if (certificates[0] instanceof X509Certificate) {
- certificate = (X509Certificate) certificates[0];
- } else {
- throw new SaslMechanism.AuthenticationException("Certificate was not X509");
- }
- final String algorithm = certificate.getSigAlgName();
- final int withIndex = algorithm.indexOf("with");
- if (withIndex <= 0) {
- throw new SaslMechanism.AuthenticationException("Unable to parse SigAlgName");
- }
- final String hashAlgorithm = algorithm.substring(0, withIndex);
- final MessageDigest messageDigest;
- // https://www.rfc-editor.org/rfc/rfc5929#section-4.1
- if ("MD5".equalsIgnoreCase(hashAlgorithm) || "SHA1".equalsIgnoreCase(hashAlgorithm)) {
- messageDigest = new SHA256.Digest();
- } else {
- try {
- messageDigest = MessageDigest.getInstance(hashAlgorithm);
- } catch (final NoSuchAlgorithmException e) {
- throw new SaslMechanism.AuthenticationException(
- "Could not instantiate message digest for " + hashAlgorithm);
- }
- }
- final byte[] encodedCertificate;
- try {
- encodedCertificate = certificate.getEncoded();
- } catch (final CertificateEncodingException e) {
- throw new SaslMechanism.AuthenticationException("Could not encode certificate");
- }
- messageDigest.update(encodedCertificate);
- return messageDigest.digest();
- }
-}
diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/DigestMd5.java b/src/main/java/eu/siacs/conversations/crypto/sasl/DigestMd5.java
deleted file mode 100644
index b75d0883f..000000000
--- a/src/main/java/eu/siacs/conversations/crypto/sasl/DigestMd5.java
+++ /dev/null
@@ -1,114 +0,0 @@
-package eu.siacs.conversations.crypto.sasl;
-
-import android.util.Base64;
-
-import java.nio.charset.Charset;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
-
-import javax.net.ssl.SSLSocket;
-
-import eu.siacs.conversations.entities.Account;
-import eu.siacs.conversations.utils.CryptoHelper;
-
-public class DigestMd5 extends SaslMechanism {
-
- public static final String MECHANISM = "DIGEST-MD5";
- private State state = State.INITIAL;
-
- public DigestMd5(final Account account) {
- super(account);
- }
-
- @Override
- public int getPriority() {
- return 10;
- }
-
- @Override
- public String getMechanism() {
- return MECHANISM;
- }
-
- @Override
- public String getResponse(final String challenge, final SSLSocket sslSocket)
- throws AuthenticationException {
- switch (state) {
- case INITIAL:
- state = State.RESPONSE_SENT;
- final String encodedResponse;
- try {
- final Tokenizer tokenizer =
- new Tokenizer(Base64.decode(challenge, Base64.DEFAULT));
- String nonce = "";
- for (final String token : tokenizer) {
- final String[] parts = token.split("=", 2);
- if (parts[0].equals("nonce")) {
- nonce = parts[1].replace("\"", "");
- } else if (parts[0].equals("rspauth")) {
- return "";
- }
- }
- final String digestUri = "xmpp/" + account.getServer();
- final String nonceCount = "00000001";
- final String x =
- account.getUsername()
- + ":"
- + account.getServer()
- + ":"
- + account.getPassword();
- final MessageDigest md = MessageDigest.getInstance("MD5");
- final byte[] y = md.digest(x.getBytes(Charset.defaultCharset()));
- final String cNonce = CryptoHelper.random(100);
- final byte[] a1 =
- CryptoHelper.concatenateByteArrays(
- y,
- (":" + nonce + ":" + cNonce)
- .getBytes(Charset.defaultCharset()));
- final String a2 = "AUTHENTICATE:" + digestUri;
- final String ha1 = CryptoHelper.bytesToHex(md.digest(a1));
- final String ha2 =
- CryptoHelper.bytesToHex(
- md.digest(a2.getBytes(Charset.defaultCharset())));
- final String kd =
- ha1 + ":" + nonce + ":" + nonceCount + ":" + cNonce + ":auth:" + ha2;
- final String response =
- CryptoHelper.bytesToHex(
- md.digest(kd.getBytes(Charset.defaultCharset())));
- final String saslString =
- "username=\""
- + account.getUsername()
- + "\",realm=\""
- + account.getServer()
- + "\",nonce=\""
- + nonce
- + "\",cnonce=\""
- + cNonce
- + "\",nc="
- + nonceCount
- + ",qop=auth,digest-uri=\""
- + digestUri
- + "\",response="
- + response
- + ",charset=utf-8";
- encodedResponse =
- Base64.encodeToString(
- saslString.getBytes(Charset.defaultCharset()), Base64.NO_WRAP);
- } catch (final NoSuchAlgorithmException e) {
- throw new AuthenticationException(e);
- }
-
- return encodedResponse;
- case RESPONSE_SENT:
- state = State.VALID_SERVER_RESPONSE;
- break;
- case VALID_SERVER_RESPONSE:
- if (challenge == null) {
- return null; // everything is fine
- }
- default:
- throw new InvalidStateException(state);
- }
- return null;
- }
-}
diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/External.java b/src/main/java/eu/siacs/conversations/crypto/sasl/External.java
deleted file mode 100644
index 6aba413a5..000000000
--- a/src/main/java/eu/siacs/conversations/crypto/sasl/External.java
+++ /dev/null
@@ -1,32 +0,0 @@
-package eu.siacs.conversations.crypto.sasl;
-
-import android.util.Base64;
-
-import javax.net.ssl.SSLSocket;
-
-import eu.siacs.conversations.entities.Account;
-
-public class External extends SaslMechanism {
-
- public static final String MECHANISM = "EXTERNAL";
-
- public External(final Account account) {
- super(account);
- }
-
- @Override
- public int getPriority() {
- return 25;
- }
-
- @Override
- public String getMechanism() {
- return MECHANISM;
- }
-
- @Override
- public String getClientFirstMessage(final SSLSocket sslSocket) {
- return Base64.encodeToString(
- account.getJid().asBareJid().toEscapedString().getBytes(), Base64.NO_WRAP);
- }
-}
diff --git a/src/main/java/eu/siacs/conversations/crypto/sasl/HashedToken.java b/src/main/java/eu/siacs/conversations/crypto/sasl/HashedToken.java
deleted file mode 100644
index d3595b9e4..000000000
--- a/src/main/java/eu/siacs/conversations/crypto/sasl/HashedToken.java
+++ /dev/null
@@ -1,190 +0,0 @@
-package eu.siacs.conversations.crypto.sasl;
-
-import android.util.Base64;
-import android.util.Log;
-
-import com.google.common.base.MoreObjects;
-import com.google.common.base.Strings;
-import com.google.common.collect.ImmutableMultimap;
-import com.google.common.collect.Multimap;
-import com.google.common.hash.HashFunction;
-import com.google.common.primitives.Bytes;
-
-import org.jetbrains.annotations.NotNull;
-
-import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.List;
-
-import javax.net.ssl.SSLSocket;
-
-import eu.siacs.conversations.Config;
-import eu.siacs.conversations.entities.Account;
-import eu.siacs.conversations.utils.SSLSockets;
-
-public abstract class HashedToken extends SaslMechanism implements ChannelBindingMechanism {
-
- private static final String PREFIX = "HT";
-
- private static final List HASH_FUNCTIONS = Arrays.asList("SHA-512", "SHA-256");
- private static final byte[] INITIATOR = "Initiator".getBytes(StandardCharsets.UTF_8);
- private static final byte[] RESPONDER = "Responder".getBytes(StandardCharsets.UTF_8);
-
- protected final ChannelBinding channelBinding;
-
- protected HashedToken(final Account account, final ChannelBinding channelBinding) {
- super(account);
- this.channelBinding = channelBinding;
- }
-
- @Override
- public int getPriority() {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public String getClientFirstMessage(final SSLSocket sslSocket) {
- final String token = Strings.nullToEmpty(this.account.getFastToken());
- final HashFunction hashing = getHashFunction(token.getBytes(StandardCharsets.UTF_8));
- final byte[] cbData = getChannelBindingData(sslSocket);
- final byte[] initiatorHashedToken =
- hashing.hashBytes(Bytes.concat(INITIATOR, cbData)).asBytes();
- final byte[] firstMessage =
- Bytes.concat(
- account.getUsername().getBytes(StandardCharsets.UTF_8),
- new byte[] {0x00},
- initiatorHashedToken);
- return Base64.encodeToString(firstMessage, Base64.NO_WRAP);
- }
-
- private byte[] getChannelBindingData(final SSLSocket sslSocket) {
- if (this.channelBinding == ChannelBinding.NONE) {
- return new byte[0];
- }
- try {
- return ChannelBindingMechanism.getChannelBindingData(sslSocket, this.channelBinding);
- } catch (final AuthenticationException e) {
- Log.e(
- Config.LOGTAG,
- account.getJid().asBareJid()
- + ": unable to retrieve channel binding data for "
- + getMechanism(),
- e);
- return new byte[0];
- }
- }
-
- @Override
- public String getResponse(final String challenge, final SSLSocket socket)
- throws AuthenticationException {
- final byte[] responderMessage;
- try {
- responderMessage = Base64.decode(challenge, Base64.NO_WRAP);
- } catch (final Exception e) {
- throw new AuthenticationException("Unable to decode responder message", e);
- }
- final String token = Strings.nullToEmpty(this.account.getFastToken());
- final HashFunction hashing = getHashFunction(token.getBytes(StandardCharsets.UTF_8));
- final byte[] cbData = getChannelBindingData(socket);
- final byte[] expectedResponderMessage =
- hashing.hashBytes(Bytes.concat(RESPONDER, cbData)).asBytes();
- if (Arrays.equals(responderMessage, expectedResponderMessage)) {
- return null;
- }
- throw new AuthenticationException("Responder message did not match");
- }
-
- protected abstract HashFunction getHashFunction(final byte[] key);
-
- public abstract Mechanism getTokenMechanism();
-
- @Override
- public String getMechanism() {
- return getTokenMechanism().name();
- }
-
- public static final class Mechanism {
- public final String hashFunction;
- public final ChannelBinding channelBinding;
-
- public Mechanism(String hashFunction, ChannelBinding channelBinding) {
- this.hashFunction = hashFunction;
- this.channelBinding = channelBinding;
- }
-
- public static Mechanism of(final String mechanism) {
- final int first = mechanism.indexOf('-');
- final int last = mechanism.lastIndexOf('-');
- if (last <= first || mechanism.length() <= last) {
- throw new IllegalArgumentException("Not a valid HashedToken name");
- }
- if (mechanism.substring(0, first).equals(PREFIX)) {
- final String hashFunction = mechanism.substring(first + 1, last);
- final String cbShortName = mechanism.substring(last + 1);
- final ChannelBinding channelBinding =
- ChannelBinding.SHORT_NAMES.inverse().get(cbShortName);
- if (channelBinding == null) {
- throw new IllegalArgumentException("Unknown channel binding " + cbShortName);
- }
- return new Mechanism(hashFunction, channelBinding);
- } else {
- throw new IllegalArgumentException("HashedToken name does not start with HT");
- }
- }
-
- public static Mechanism ofOrNull(final String mechanism) {
- try {
- return mechanism == null ? null : of(mechanism);
- } catch (final IllegalArgumentException e) {
- return null;
- }
- }
-
- public static Multimap of(final Collection mechanisms) {
- final ImmutableMultimap.Builder builder =
- ImmutableMultimap.builder();
- for (final String name : mechanisms) {
- try {
- final Mechanism mechanism = Mechanism.of(name);
- builder.put(mechanism.hashFunction, mechanism.channelBinding);
- } catch (final IllegalArgumentException ignored) {
- }
- }
- return builder.build();
- }
-
- public static Mechanism best(
- final Collection mechanisms, final SSLSockets.Version sslVersion) {
- final Multimap multimap = of(mechanisms);
- for (final String hashFunction : HASH_FUNCTIONS) {
- final Collection