Merge branch 'master' of https://codeberg.org/iNPUTmice/Conversations
This commit is contained in:
parent
49ea241655
commit
07535bdb8a
320 changed files with 11298 additions and 2963 deletions
28
build.gradle
28
build.gradle
|
@ -44,14 +44,16 @@ configurations {
|
||||||
dependencies {
|
dependencies {
|
||||||
androidTestImplementation 'tools.fastlane:screengrab:2.1.1'
|
androidTestImplementation 'tools.fastlane:screengrab:2.1.1'
|
||||||
androidTestImplementation 'junit:junit:4.13.2'
|
androidTestImplementation 'junit:junit:4.13.2'
|
||||||
androidTestImplementation 'androidx.test:runner:1.3.0'
|
androidTestImplementation 'androidx.test:runner:1.6.2'
|
||||||
androidTestImplementation 'androidx.test:rules:1.3.0'
|
androidTestImplementation 'androidx.test:rules:1.6.1'
|
||||||
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
|
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
|
||||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
|
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
|
||||||
|
|
||||||
implementation "androidx.core:core:1.10.1"
|
implementation "androidx.core:core:1.13.1"
|
||||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'
|
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.2'
|
||||||
|
|
||||||
|
implementation project(':libs:annotation')
|
||||||
|
annotationProcessor project(':libs:annotation-processor')
|
||||||
|
|
||||||
implementation 'androidx.viewpager:viewpager:1.0.0'
|
implementation 'androidx.viewpager:viewpager:1.0.0'
|
||||||
|
|
||||||
|
@ -65,17 +67,17 @@ dependencies {
|
||||||
conversationsPlaystoreImplementation("com.android.installreferrer:installreferrer:2.2")
|
conversationsPlaystoreImplementation("com.android.installreferrer:installreferrer:2.2")
|
||||||
quicksyPlaystoreImplementation 'com.google.android.gms:play-services-auth-api-phone:18.1.0'
|
quicksyPlaystoreImplementation 'com.google.android.gms:play-services-auth-api-phone:18.1.0'
|
||||||
implementation 'com.github.open-keychain.open-keychain:openpgp-api:v5.7.1'
|
implementation 'com.github.open-keychain.open-keychain:openpgp-api:v5.7.1'
|
||||||
implementation("com.github.CanHub:Android-Image-Cropper:2.0.0")
|
implementation("com.github.CanHub:Android-Image-Cropper:2.2.0")
|
||||||
implementation 'androidx.appcompat:appcompat:1.7.0'
|
implementation 'androidx.appcompat:appcompat:1.7.0'
|
||||||
implementation 'androidx.exifinterface:exifinterface:1.3.7'
|
implementation 'androidx.exifinterface:exifinterface:1.3.7'
|
||||||
implementation 'androidx.cardview:cardview:1.0.0'
|
implementation 'androidx.cardview:cardview:1.0.0'
|
||||||
implementation "androidx.preference:preference:1.2.1"
|
implementation "androidx.preference:preference:1.2.1"
|
||||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
||||||
implementation 'com.google.android.material:material:1.12.0'
|
implementation 'com.google.android.material:material:1.12.0'
|
||||||
implementation 'androidx.work:work-runtime:2.9.0'
|
implementation 'androidx.work:work-runtime:2.9.1'
|
||||||
|
|
||||||
implementation "androidx.emoji2:emoji2:1.4.0"
|
implementation "androidx.emoji2:emoji2:1.5.0"
|
||||||
freeImplementation "androidx.emoji2:emoji2-bundled:1.4.0"
|
freeImplementation "androidx.emoji2:emoji2-bundled:1.5.0"
|
||||||
|
|
||||||
implementation 'org.bouncycastle:bcmail-jdk18on:1.78.1'
|
implementation 'org.bouncycastle:bcmail-jdk18on:1.78.1'
|
||||||
implementation 'com.google.zxing:core:3.5.3'
|
implementation 'com.google.zxing:core:3.5.3'
|
||||||
|
@ -92,7 +94,7 @@ dependencies {
|
||||||
|
|
||||||
implementation 'org.jxmpp:jxmpp-jid:1.0.3'
|
implementation 'org.jxmpp:jxmpp-jid:1.0.3'
|
||||||
implementation 'org.jxmpp:jxmpp-stringprep-libidn:1.0.3'
|
implementation 'org.jxmpp:jxmpp-stringprep-libidn:1.0.3'
|
||||||
implementation 'org.osmdroid:osmdroid-android:6.1.11'
|
implementation 'org.osmdroid:osmdroid-android:6.1.16'
|
||||||
implementation 'org.hsluv:hsluv:0.2'
|
implementation 'org.hsluv:hsluv:0.2'
|
||||||
implementation 'org.conscrypt:conscrypt-android:2.5.2'
|
implementation 'org.conscrypt:conscrypt-android:2.5.2'
|
||||||
implementation 'me.drakeet.support:toastcompat:1.1.0'
|
implementation 'me.drakeet.support:toastcompat:1.1.0'
|
||||||
|
@ -102,11 +104,11 @@ dependencies {
|
||||||
implementation "com.squareup.retrofit2:converter-gson:2.11.0"
|
implementation "com.squareup.retrofit2:converter-gson:2.11.0"
|
||||||
implementation "com.squareup.okhttp3:okhttp:4.12.0"
|
implementation "com.squareup.okhttp3:okhttp:4.12.0"
|
||||||
|
|
||||||
implementation 'com.google.guava:guava:32.1.3-android'
|
implementation 'com.google.guava:guava:33.0.0-android'
|
||||||
implementation 'io.michaelrocks:libphonenumber-android:8.13.35'
|
implementation 'io.michaelrocks:libphonenumber-android:8.13.35'
|
||||||
implementation 'im.conversations.webrtc:webrtc-android:119.0.1'
|
implementation 'im.conversations.webrtc:webrtc-android:119.0.1'
|
||||||
implementation 'io.github.nishkarsh:android-permissions:2.1.6'
|
implementation 'io.github.nishkarsh:android-permissions:2.1.6'
|
||||||
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
implementation 'androidx.recyclerview:recyclerview:1.3.2'
|
||||||
implementation 'androidx.documentfile:documentfile:1.0.1'
|
implementation 'androidx.documentfile:documentfile:1.0.1'
|
||||||
implementation 'androidx.browser:browser:1.8.0'
|
implementation 'androidx.browser:browser:1.8.0'
|
||||||
implementation 'com.github.martin-stone:hsv-alpha-color-picker-android:3.1.0'
|
implementation 'com.github.martin-stone:hsv-alpha-color-picker-android:3.1.0'
|
||||||
|
|
20
libs/annotation-processor/build.gradle
Normal file
20
libs/annotation-processor/build.gradle
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
apply plugin: "java-library"
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
java {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
|
}
|
||||||
|
dependencies {
|
||||||
|
|
||||||
|
implementation project(':libs:annotation')
|
||||||
|
|
||||||
|
annotationProcessor 'com.google.auto.service:auto-service:1.0.1'
|
||||||
|
api 'com.google.auto.service:auto-service-annotations:1.0.1'
|
||||||
|
implementation 'com.google.guava:guava:31.1-jre'
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,185 @@
|
||||||
|
package im.conversations.android.annotation.processor;
|
||||||
|
|
||||||
|
import com.google.auto.service.AutoService;
|
||||||
|
import com.google.common.base.CaseFormat;
|
||||||
|
import com.google.common.base.Objects;
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
|
||||||
|
import im.conversations.android.annotation.XmlElement;
|
||||||
|
import im.conversations.android.annotation.XmlPackage;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.PrintWriter;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import javax.annotation.processing.AbstractProcessor;
|
||||||
|
import javax.annotation.processing.Processor;
|
||||||
|
import javax.annotation.processing.RoundEnvironment;
|
||||||
|
import javax.annotation.processing.SupportedAnnotationTypes;
|
||||||
|
import javax.annotation.processing.SupportedSourceVersion;
|
||||||
|
import javax.lang.model.SourceVersion;
|
||||||
|
import javax.lang.model.element.Element;
|
||||||
|
import javax.lang.model.element.ExecutableElement;
|
||||||
|
import javax.lang.model.element.Modifier;
|
||||||
|
import javax.lang.model.element.PackageElement;
|
||||||
|
import javax.lang.model.element.TypeElement;
|
||||||
|
import javax.lang.model.util.ElementFilter;
|
||||||
|
import javax.tools.JavaFileObject;
|
||||||
|
|
||||||
|
@AutoService(Processor.class)
|
||||||
|
@SupportedSourceVersion(SourceVersion.RELEASE_17)
|
||||||
|
@SupportedAnnotationTypes("im.conversations.android.annotation.XmlElement")
|
||||||
|
public class XmlElementProcessor extends AbstractProcessor {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
|
||||||
|
final Set<? extends Element> elements =
|
||||||
|
roundEnvironment.getElementsAnnotatedWith(XmlElement.class);
|
||||||
|
final ImmutableMap.Builder<Id, String> builder = ImmutableMap.builder();
|
||||||
|
for (final Element element : elements) {
|
||||||
|
if (element instanceof final TypeElement typeElement) {
|
||||||
|
final Id id = of(typeElement);
|
||||||
|
builder.put(id, typeElement.getQualifiedName().toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
final ImmutableMap<Id, String> maps = builder.build();
|
||||||
|
if (maps.isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final JavaFileObject extensionFile;
|
||||||
|
try {
|
||||||
|
extensionFile =
|
||||||
|
processingEnv
|
||||||
|
.getFiler()
|
||||||
|
.createSourceFile("im.conversations.android.xmpp.Extensions");
|
||||||
|
} catch (final IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
try (final PrintWriter out = new PrintWriter(extensionFile.openWriter())) {
|
||||||
|
out.println("package im.conversations.android.xmpp;");
|
||||||
|
out.println("import com.google.common.collect.BiMap;");
|
||||||
|
out.println("import com.google.common.collect.ImmutableBiMap;");
|
||||||
|
out.println("import im.conversations.android.xmpp.ExtensionFactory;");
|
||||||
|
out.println("import im.conversations.android.xmpp.model.Extension;");
|
||||||
|
out.print("\n");
|
||||||
|
out.println("public final class Extensions {");
|
||||||
|
out.println(
|
||||||
|
"public static final BiMap<ExtensionFactory.Id, Class<? extends Extension>>"
|
||||||
|
+ " EXTENSION_CLASS_MAP;");
|
||||||
|
out.println("static {");
|
||||||
|
out.println(
|
||||||
|
"final var builder = new ImmutableBiMap.Builder<ExtensionFactory.Id, Class<?"
|
||||||
|
+ " extends Extension>>();");
|
||||||
|
for (final Map.Entry<Id, String> entry : maps.entrySet()) {
|
||||||
|
Id id = entry.getKey();
|
||||||
|
String clazz = entry.getValue();
|
||||||
|
out.format(
|
||||||
|
"builder.put(new ExtensionFactory.Id(\"%s\",\"%s\"),%s.class);",
|
||||||
|
id.name, id.namespace, clazz);
|
||||||
|
out.print("\n");
|
||||||
|
}
|
||||||
|
out.println("EXTENSION_CLASS_MAP = builder.build();");
|
||||||
|
out.println("}");
|
||||||
|
out.println(" private Extensions() {}");
|
||||||
|
out.println("}");
|
||||||
|
// writing generated file to out …
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Id of(final TypeElement typeElement) {
|
||||||
|
final XmlElement xmlElement = typeElement.getAnnotation(XmlElement.class);
|
||||||
|
final PackageElement packageElement = getPackageElement(typeElement);
|
||||||
|
final XmlPackage xmlPackage =
|
||||||
|
packageElement == null ? null : packageElement.getAnnotation(XmlPackage.class);
|
||||||
|
if (xmlElement == null) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
String.format(
|
||||||
|
"%s is not annotated as @XmlElement",
|
||||||
|
typeElement.getQualifiedName().toString()));
|
||||||
|
}
|
||||||
|
final String packageNamespace = xmlPackage == null ? null : xmlPackage.namespace();
|
||||||
|
final String elementName = xmlElement.name();
|
||||||
|
final String elementNamespace = xmlElement.namespace();
|
||||||
|
final String namespace;
|
||||||
|
if (!Strings.isNullOrEmpty(elementNamespace)) {
|
||||||
|
namespace = elementNamespace;
|
||||||
|
} else if (!Strings.isNullOrEmpty(packageNamespace)) {
|
||||||
|
namespace = packageNamespace;
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
String.format(
|
||||||
|
"%s does not declare a namespace",
|
||||||
|
typeElement.getQualifiedName().toString()));
|
||||||
|
}
|
||||||
|
if (!hasEmptyDefaultConstructor(typeElement)) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
String.format(
|
||||||
|
"%s does not have an empty default constructor",
|
||||||
|
typeElement.getQualifiedName().toString()));
|
||||||
|
}
|
||||||
|
final String name;
|
||||||
|
if (Strings.isNullOrEmpty(elementName)) {
|
||||||
|
name =
|
||||||
|
CaseFormat.UPPER_CAMEL.to(
|
||||||
|
CaseFormat.LOWER_HYPHEN, typeElement.getSimpleName().toString());
|
||||||
|
} else {
|
||||||
|
name = elementName;
|
||||||
|
}
|
||||||
|
return new Id(name, namespace);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static PackageElement getPackageElement(final TypeElement typeElement) {
|
||||||
|
final Element parent = typeElement.getEnclosingElement();
|
||||||
|
if (parent instanceof PackageElement) {
|
||||||
|
return (PackageElement) parent;
|
||||||
|
} else {
|
||||||
|
final Element nextParent = parent.getEnclosingElement();
|
||||||
|
if (nextParent instanceof PackageElement) {
|
||||||
|
return (PackageElement) nextParent;
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean hasEmptyDefaultConstructor(final TypeElement typeElement) {
|
||||||
|
final List<ExecutableElement> constructors =
|
||||||
|
ElementFilter.constructorsIn(typeElement.getEnclosedElements());
|
||||||
|
for (final ExecutableElement constructor : constructors) {
|
||||||
|
if (constructor.getParameters().isEmpty()
|
||||||
|
&& constructor.getModifiers().contains(Modifier.PUBLIC)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Id {
|
||||||
|
public final String name;
|
||||||
|
public final String namespace;
|
||||||
|
|
||||||
|
public Id(String name, String namespace) {
|
||||||
|
this.name = name;
|
||||||
|
this.namespace = namespace;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
Id id = (Id) o;
|
||||||
|
return Objects.equal(name, id.name) && Objects.equal(namespace, id.namespace);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hashCode(name, namespace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
6
libs/annotation/build.gradle
Normal file
6
libs/annotation/build.gradle
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
apply plugin: "java-library"
|
||||||
|
|
||||||
|
java {
|
||||||
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
package im.conversations.android.annotation;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@Target({ElementType.TYPE})
|
||||||
|
public @interface XmlElement {
|
||||||
|
|
||||||
|
String name() default "";
|
||||||
|
|
||||||
|
String namespace() default "";
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package im.conversations.android.annotation;
|
||||||
|
|
||||||
|
import java.lang.annotation.ElementType;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@Target(ElementType.PACKAGE)
|
||||||
|
public @interface XmlPackage {
|
||||||
|
String namespace();
|
||||||
|
}
|
1
proguard-rules.pro
vendored
1
proguard-rules.pro
vendored
|
@ -1,6 +1,7 @@
|
||||||
-dontobfuscate
|
-dontobfuscate
|
||||||
|
|
||||||
-keep class eu.siacs.conversations.**
|
-keep class eu.siacs.conversations.**
|
||||||
|
-keep class im.conversations.**
|
||||||
|
|
||||||
-keep class org.whispersystems.**
|
-keep class org.whispersystems.**
|
||||||
|
|
||||||
|
|
|
@ -1 +1,3 @@
|
||||||
|
include ':libs:annotation', ':libs:annotation-processor:'
|
||||||
|
|
||||||
rootProject.name = 'Conversations'
|
rootProject.name = 'Conversations'
|
||||||
|
|
5
src/cheogram/res/xml/cache_paths.xml
Normal file
5
src/cheogram/res/xml/cache_paths.xml
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<cache-path name="cache_media" path="cache/media"/>
|
||||||
|
<cache-path name="cache_avatars" path="cache/avatars"/>
|
||||||
|
</paths>
|
|
@ -142,10 +142,6 @@
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
|
|
||||||
<receiver
|
|
||||||
android:name=".receiver.WorkManagerEventReceiver"
|
|
||||||
android:exported="false" />
|
|
||||||
|
|
||||||
<receiver
|
<receiver
|
||||||
android:name=".receiver.SystemEventReceiver"
|
android:name=".receiver.SystemEventReceiver"
|
||||||
android:exported="false">
|
android:exported="false">
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2015-2022 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 de.gultsch.minidns;
|
||||||
|
|
||||||
|
import org.minidns.MiniDnsException;
|
||||||
|
import org.minidns.dnsmessage.Question;
|
||||||
|
import org.minidns.dnsmessage.DnsMessage.RESPONSE_CODE;
|
||||||
|
|
||||||
|
import java.io.Serial;
|
||||||
|
|
||||||
|
public class ResolutionUnsuccessfulException extends MiniDnsException {
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@Serial
|
||||||
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
public final Question question;
|
||||||
|
public final RESPONSE_CODE responseCode;
|
||||||
|
|
||||||
|
public ResolutionUnsuccessfulException(Question question, RESPONSE_CODE responseCode) {
|
||||||
|
super("Asking for " + question + " yielded an error response " + responseCode);
|
||||||
|
this.question = question;
|
||||||
|
this.responseCode = responseCode;
|
||||||
|
}
|
||||||
|
}
|
178
src/main/java/de/gultsch/minidns/ResolverResult.java
Normal file
178
src/main/java/de/gultsch/minidns/ResolverResult.java
Normal file
|
@ -0,0 +1,178 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2015-2022 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 de.gultsch.minidns;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.minidns.MiniDnsException;
|
||||||
|
import org.minidns.MiniDnsException.NullResultException;
|
||||||
|
import org.minidns.dnsmessage.DnsMessage;
|
||||||
|
import org.minidns.dnsmessage.Question;
|
||||||
|
import org.minidns.dnsqueryresult.DnsQueryResult;
|
||||||
|
import org.minidns.dnsmessage.DnsMessage.RESPONSE_CODE;
|
||||||
|
import org.minidns.dnssec.DnssecResultNotAuthenticException;
|
||||||
|
import org.minidns.dnssec.DnssecUnverifiedReason;
|
||||||
|
import org.minidns.record.Data;
|
||||||
|
|
||||||
|
public class ResolverResult<D extends Data> {
|
||||||
|
|
||||||
|
protected final Question question;
|
||||||
|
private final RESPONSE_CODE responseCode;
|
||||||
|
private final Set<D> data;
|
||||||
|
private final boolean isAuthenticData;
|
||||||
|
protected final Set<DnssecUnverifiedReason> unverifiedReasons;
|
||||||
|
protected final DnsMessage answer;
|
||||||
|
protected final DnsQueryResult result;
|
||||||
|
|
||||||
|
public ResolverResult(Question question, DnsQueryResult result, Set<DnssecUnverifiedReason> unverifiedReasons) throws NullResultException {
|
||||||
|
// TODO: Is this null check still needed?
|
||||||
|
if (result == null) {
|
||||||
|
throw new MiniDnsException.NullResultException(question.asMessageBuilder().build());
|
||||||
|
}
|
||||||
|
|
||||||
|
this.result = result;
|
||||||
|
|
||||||
|
DnsMessage answer = result.response;
|
||||||
|
this.question = question;
|
||||||
|
this.responseCode = answer.responseCode;
|
||||||
|
this.answer = answer;
|
||||||
|
|
||||||
|
Set<D> r = answer.getAnswersFor(question);
|
||||||
|
if (r == null) {
|
||||||
|
this.data = Collections.emptySet();
|
||||||
|
} else {
|
||||||
|
this.data = Collections.unmodifiableSet(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unverifiedReasons == null) {
|
||||||
|
this.unverifiedReasons = null;
|
||||||
|
isAuthenticData = false;
|
||||||
|
} else {
|
||||||
|
this.unverifiedReasons = Collections.unmodifiableSet(unverifiedReasons);
|
||||||
|
isAuthenticData = this.unverifiedReasons.isEmpty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean wasSuccessful() {
|
||||||
|
return responseCode == RESPONSE_CODE.NO_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<D> getAnswers() {
|
||||||
|
throwIseIfErrorResponse();
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<D> getAnswersOrEmptySet() {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RESPONSE_CODE getResponseCode() {
|
||||||
|
return responseCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isAuthenticData() {
|
||||||
|
throwIseIfErrorResponse();
|
||||||
|
return isAuthenticData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the reasons the result could not be verified if any exists.
|
||||||
|
*
|
||||||
|
* @return The reasons the result could not be verified or <code>null</code>.
|
||||||
|
*/
|
||||||
|
public Set<DnssecUnverifiedReason> getUnverifiedReasons() {
|
||||||
|
throwIseIfErrorResponse();
|
||||||
|
return unverifiedReasons;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Question getQuestion() {
|
||||||
|
return question;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void throwIfErrorResponse() throws ResolutionUnsuccessfulException {
|
||||||
|
ResolutionUnsuccessfulException resolutionUnsuccessfulException = getResolutionUnsuccessfulException();
|
||||||
|
if (resolutionUnsuccessfulException != null) throw resolutionUnsuccessfulException;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ResolutionUnsuccessfulException resolutionUnsuccessfulException;
|
||||||
|
|
||||||
|
public ResolutionUnsuccessfulException getResolutionUnsuccessfulException() {
|
||||||
|
if (wasSuccessful()) return null;
|
||||||
|
|
||||||
|
if (resolutionUnsuccessfulException == null) {
|
||||||
|
resolutionUnsuccessfulException = new ResolutionUnsuccessfulException(question, responseCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolutionUnsuccessfulException;
|
||||||
|
}
|
||||||
|
|
||||||
|
private DnssecResultNotAuthenticException dnssecResultNotAuthenticException;
|
||||||
|
|
||||||
|
public DnssecResultNotAuthenticException getDnssecResultNotAuthenticException() {
|
||||||
|
if (!wasSuccessful())
|
||||||
|
return null;
|
||||||
|
if (isAuthenticData)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
if (dnssecResultNotAuthenticException == null) {
|
||||||
|
dnssecResultNotAuthenticException = DnssecResultNotAuthenticException.from(getUnverifiedReasons());
|
||||||
|
}
|
||||||
|
|
||||||
|
return dnssecResultNotAuthenticException;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the raw answer DNS message we received. <b>This is likely not what you want</b>, try {@link #getAnswers()} instead.
|
||||||
|
*
|
||||||
|
* @return the raw answer DNS Message.
|
||||||
|
* @see #getAnswers()
|
||||||
|
*/
|
||||||
|
public DnsMessage getRawAnswer() {
|
||||||
|
return answer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DnsQueryResult getDnsQueryResult() {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final String toString() {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
|
sb.append(getClass().getName()).append('\n')
|
||||||
|
.append("Question: ").append(question).append('\n')
|
||||||
|
.append("Response Code: ").append(responseCode).append('\n');
|
||||||
|
|
||||||
|
if (responseCode == RESPONSE_CODE.NO_ERROR) {
|
||||||
|
if (isAuthenticData) {
|
||||||
|
sb.append("Results verified via DNSSEC\n");
|
||||||
|
}
|
||||||
|
if (hasUnverifiedReasons()) {
|
||||||
|
sb.append(unverifiedReasons).append('\n');
|
||||||
|
}
|
||||||
|
sb.append(answer.answerSection);
|
||||||
|
}
|
||||||
|
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean hasUnverifiedReasons() {
|
||||||
|
return unverifiedReasons != null && !unverifiedReasons.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void throwIseIfErrorResponse() {
|
||||||
|
ResolutionUnsuccessfulException resolutionUnsuccessfulException = getResolutionUnsuccessfulException();
|
||||||
|
if (resolutionUnsuccessfulException != null)
|
||||||
|
throw new IllegalStateException("Can not perform operation because the DNS resolution was unsuccessful",
|
||||||
|
resolutionUnsuccessfulException);
|
||||||
|
}
|
||||||
|
}
|
|
@ -10,6 +10,8 @@ import androidx.preference.PreferenceManager;
|
||||||
|
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
|
|
||||||
|
import java.security.SecureRandom;
|
||||||
|
|
||||||
public class AppSettings {
|
public class AppSettings {
|
||||||
|
|
||||||
public static final String KEEP_FOREGROUND_SERVICE = "enable_foreground_service";
|
public static final String KEEP_FOREGROUND_SERVICE = "enable_foreground_service";
|
||||||
|
@ -46,6 +48,9 @@ public class AppSettings {
|
||||||
public static final String LARGE_FONT = "large_font";
|
public static final String LARGE_FONT = "large_font";
|
||||||
public static final String SHOW_LINK_PREVIEWS = "show_link_previews";
|
public static final String SHOW_LINK_PREVIEWS = "show_link_previews";
|
||||||
|
|
||||||
|
private static final String ACCEPT_INVITES_FROM_STRANGERS = "accept_invites_from_strangers";
|
||||||
|
private static final String INSTALLATION_ID = "im.conversations.android.install_id";
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
|
|
||||||
public AppSettings(final Context context) {
|
public AppSettings(final Context context) {
|
||||||
|
@ -143,4 +148,25 @@ public class AppSettings {
|
||||||
public boolean isRequireChannelBinding() {
|
public boolean isRequireChannelBinding() {
|
||||||
return getBooleanPreference(REQUIRE_CHANNEL_BINDING, R.bool.require_channel_binding);
|
return getBooleanPreference(REQUIRE_CHANNEL_BINDING, R.bool.require_channel_binding);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public synchronized long getInstallationId() {
|
||||||
|
final var sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
final long existing = sharedPreferences.getLong(INSTALLATION_ID, 0);
|
||||||
|
if (existing != 0) {
|
||||||
|
return existing;
|
||||||
|
}
|
||||||
|
final var secureRandom = new SecureRandom();
|
||||||
|
final var installationId = secureRandom.nextLong();
|
||||||
|
sharedPreferences.edit().putLong(INSTALLATION_ID, installationId).apply();
|
||||||
|
return installationId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public synchronized void resetInstallationId() {
|
||||||
|
final var secureRandom = new SecureRandom();
|
||||||
|
final var installationId = secureRandom.nextLong();
|
||||||
|
PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
|
.edit()
|
||||||
|
.putLong(INSTALLATION_ID, installationId)
|
||||||
|
.apply();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package eu.siacs.conversations;
|
package eu.siacs.conversations;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
|
@ -15,9 +16,17 @@ import eu.siacs.conversations.utils.ThemeHelper;
|
||||||
|
|
||||||
public class Conversations extends Application {
|
public class Conversations extends Application {
|
||||||
|
|
||||||
|
@SuppressLint("StaticFieldLeak")
|
||||||
|
private static Context CONTEXT;
|
||||||
|
|
||||||
|
public static Context getContext() {
|
||||||
|
return Conversations.CONTEXT;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
super.onCreate();
|
super.onCreate();
|
||||||
|
CONTEXT = this.getApplicationContext();
|
||||||
ExceptionHelper.init(getApplicationContext());
|
ExceptionHelper.init(getApplicationContext());
|
||||||
applyThemeSettings();
|
applyThemeSettings();
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,7 +61,6 @@ import eu.siacs.conversations.xml.Element;
|
||||||
import eu.siacs.conversations.xml.Namespace;
|
import eu.siacs.conversations.xml.Namespace;
|
||||||
import eu.siacs.conversations.xmpp.Jid;
|
import eu.siacs.conversations.xmpp.Jid;
|
||||||
import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded;
|
import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded;
|
||||||
import eu.siacs.conversations.xmpp.OnIqPacketReceived;
|
|
||||||
import eu.siacs.conversations.xmpp.jingle.DescriptionTransport;
|
import eu.siacs.conversations.xmpp.jingle.DescriptionTransport;
|
||||||
import eu.siacs.conversations.xmpp.jingle.OmemoVerification;
|
import eu.siacs.conversations.xmpp.jingle.OmemoVerification;
|
||||||
import eu.siacs.conversations.xmpp.jingle.OmemoVerifiedRtpContentMap;
|
import eu.siacs.conversations.xmpp.jingle.OmemoVerifiedRtpContentMap;
|
||||||
|
@ -70,8 +69,7 @@ import eu.siacs.conversations.xmpp.jingle.stanzas.IceUdpTransportInfo;
|
||||||
import eu.siacs.conversations.xmpp.jingle.stanzas.OmemoVerifiedIceUdpTransportInfo;
|
import eu.siacs.conversations.xmpp.jingle.stanzas.OmemoVerifiedIceUdpTransportInfo;
|
||||||
import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription;
|
import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription;
|
||||||
import eu.siacs.conversations.xmpp.pep.PublishOptions;
|
import eu.siacs.conversations.xmpp.pep.PublishOptions;
|
||||||
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
|
import im.conversations.android.xmpp.model.stanza.Iq;
|
||||||
import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
|
|
||||||
|
|
||||||
public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
||||||
|
|
||||||
|
@ -392,20 +390,18 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
||||||
Log.d(Config.LOGTAG, getLogprefix(account) + "publishOwnDeviceIdIfNeeded called, but PEP is broken. Ignoring... ");
|
Log.d(Config.LOGTAG, getLogprefix(account) + "publishOwnDeviceIdIfNeeded called, but PEP is broken. Ignoring... ");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveDeviceIds(account.getJid().asBareJid());
|
Iq packet = mXmppConnectionService.getIqGenerator().retrieveDeviceIds(account.getJid().asBareJid());
|
||||||
mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
|
mXmppConnectionService.sendIqPacket(account, packet, response -> {
|
||||||
@Override
|
if (response.getType() == Iq.Type.TIMEOUT) {
|
||||||
public void onIqPacketReceived(Account account, IqPacket packet) {
|
Log.d(Config.LOGTAG, getLogprefix(account) + "Timeout received while retrieving own Device Ids.");
|
||||||
if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
|
} else {
|
||||||
Log.d(Config.LOGTAG, getLogprefix(account) + "Timeout received while retrieving own Device Ids.");
|
//TODO consider calling registerDevices only after item-not-found to account for broken PEPs
|
||||||
} else {
|
final Element item = IqParser.getItem(response);
|
||||||
//TODO consider calling registerDevices only after item-not-found to account for broken PEPs
|
final Set<Integer> deviceIds = IqParser.deviceIds(item);
|
||||||
Element item = mXmppConnectionService.getIqParser().getItem(packet);
|
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": retrieved own device list: " + deviceIds);
|
||||||
Set<Integer> deviceIds = mXmppConnectionService.getIqParser().deviceIds(item);
|
registerDevices(account.getJid().asBareJid(), deviceIds);
|
||||||
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": retrieved own device list: " + deviceIds);
|
|
||||||
registerDevices(account.getJid().asBareJid(), deviceIds);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -455,40 +451,37 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
||||||
|
|
||||||
private void publishDeviceIdsAndRefineAccessModel(final Set<Integer> ids, final boolean firstAttempt) {
|
private void publishDeviceIdsAndRefineAccessModel(final Set<Integer> ids, final boolean firstAttempt) {
|
||||||
final Bundle publishOptions = account.getXmppConnection().getFeatures().pepPublishOptions() ? PublishOptions.openAccess() : null;
|
final Bundle publishOptions = account.getXmppConnection().getFeatures().pepPublishOptions() ? PublishOptions.openAccess() : null;
|
||||||
IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(ids, publishOptions);
|
final var publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(ids, publishOptions);
|
||||||
mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() {
|
mXmppConnectionService.sendIqPacket(account, publish, response -> {
|
||||||
@Override
|
final Element error = response.getType() == Iq.Type.ERROR ? response.findChild("error") : null;
|
||||||
public void onIqPacketReceived(Account account, IqPacket packet) {
|
final boolean preConditionNotMet = PublishOptions.preconditionNotMet(response);
|
||||||
final Element error = packet.getType() == IqPacket.TYPE.ERROR ? packet.findChild("error") : null;
|
if (firstAttempt && preConditionNotMet) {
|
||||||
final boolean preConditionNotMet = PublishOptions.preconditionNotMet(packet);
|
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": precondition wasn't met for device list. pushing node configuration");
|
||||||
if (firstAttempt && preConditionNotMet) {
|
mXmppConnectionService.pushNodeConfiguration(account, AxolotlService.PEP_DEVICE_LIST, publishOptions, new XmppConnectionService.OnConfigurationPushed() {
|
||||||
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": precondition wasn't met for device list. pushing node configuration");
|
@Override
|
||||||
mXmppConnectionService.pushNodeConfiguration(account, AxolotlService.PEP_DEVICE_LIST, publishOptions, new XmppConnectionService.OnConfigurationPushed() {
|
public void onPushSucceeded() {
|
||||||
@Override
|
publishDeviceIdsAndRefineAccessModel(ids, false);
|
||||||
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"));
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@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 (response.getType() == Iq.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" + response.findChild("error"));
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -506,26 +499,23 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
||||||
verifier.initSign(x509PrivateKey, SECURE_RANDOM);
|
verifier.initSign(x509PrivateKey, SECURE_RANDOM);
|
||||||
verifier.update(axolotlPublicKey.serialize());
|
verifier.update(axolotlPublicKey.serialize());
|
||||||
byte[] signature = verifier.sign();
|
byte[] signature = verifier.sign();
|
||||||
IqPacket packet = mXmppConnectionService.getIqGenerator().publishVerification(signature, chain, getOwnDeviceId());
|
final Iq packet = mXmppConnectionService.getIqGenerator().publishVerification(signature, chain, getOwnDeviceId());
|
||||||
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ": publish verification for device " + getOwnDeviceId());
|
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ": publish verification for device " + getOwnDeviceId());
|
||||||
mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
|
mXmppConnectionService.sendIqPacket(account, packet, response -> {
|
||||||
@Override
|
String node = AxolotlService.PEP_VERIFICATION + ":" + getOwnDeviceId();
|
||||||
public void onIqPacketReceived(final Account account, IqPacket packet) {
|
mXmppConnectionService.pushNodeConfiguration(account, node, PublishOptions.openAccess(), new XmppConnectionService.OnConfigurationPushed() {
|
||||||
String node = AxolotlService.PEP_VERIFICATION + ":" + getOwnDeviceId();
|
@Override
|
||||||
mXmppConnectionService.pushNodeConfiguration(account, node, PublishOptions.openAccess(), new XmppConnectionService.OnConfigurationPushed() {
|
public void onPushSucceeded() {
|
||||||
@Override
|
Log.d(Config.LOGTAG, getLogprefix(account) + "configured verification node to be world readable");
|
||||||
public void onPushSucceeded() {
|
publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe);
|
||||||
Log.d(Config.LOGTAG, getLogprefix(account) + "configured verification node to be world readable");
|
}
|
||||||
publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPushFailed() {
|
public void onPushFailed() {
|
||||||
Log.d(Config.LOGTAG, getLogprefix(account) + "unable to set access model on verification node");
|
Log.d(Config.LOGTAG, getLogprefix(account) + "unable to set access model on verification node");
|
||||||
publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe);
|
publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
});
|
});
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
|
@ -549,109 +539,106 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
||||||
if (this.changeAccessMode.get()) {
|
if (this.changeAccessMode.get()) {
|
||||||
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": server gained publish-options capabilities. changing access model");
|
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": server gained publish-options capabilities. changing access model");
|
||||||
}
|
}
|
||||||
IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(account.getJid().asBareJid(), getOwnDeviceId());
|
final Iq packet = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(account.getJid().asBareJid(), getOwnDeviceId());
|
||||||
mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
|
mXmppConnectionService.sendIqPacket(account, packet, response -> {
|
||||||
@Override
|
|
||||||
public void onIqPacketReceived(Account account, IqPacket packet) {
|
|
||||||
|
|
||||||
if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
|
if (response.getType() == Iq.Type.TIMEOUT) {
|
||||||
return; //ignore timeout. do nothing
|
return; //ignore timeout. do nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
if (response.getType() == Iq.Type.ERROR) {
|
||||||
|
Element error = response.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" + response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PreKeyBundle bundle = IqParser.bundle(response);
|
||||||
|
final Map<Integer, ECPublicKey> keys = IqParser.preKeyPublics(response);
|
||||||
|
boolean flush = false;
|
||||||
|
if (bundle == null) {
|
||||||
|
Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received invalid bundle:" + response);
|
||||||
|
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:" + response);
|
||||||
|
}
|
||||||
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (packet.getType() == IqPacket.TYPE.ERROR) {
|
// Validate signedPreKeyRecord + ID
|
||||||
Element error = packet.findChild("error");
|
SignedPreKeyRecord signedPreKeyRecord;
|
||||||
if (error == null || !error.hasChild("item-not-found")) {
|
int numSignedPreKeys = axolotlStore.getSignedPreKeysCount();
|
||||||
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<Integer, ECPublicKey> 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 {
|
try {
|
||||||
boolean changed = false;
|
signedPreKeyRecord = axolotlStore.loadSignedPreKey(bundle.getSignedPreKeyId());
|
||||||
// Validate IdentityKey
|
if (flush
|
||||||
IdentityKeyPair identityKeyPair = axolotlStore.getIdentityKeyPair();
|
|| !bundle.getSignedPreKey().equals(signedPreKeyRecord.getKeyPair().getPublicKey())
|
||||||
if (flush || !identityKeyPair.getPublicKey().equals(bundle.getIdentityKey())) {
|
|| !Arrays.equals(bundle.getSignedPreKeySignature(), signedPreKeyRecord.getSignature())) {
|
||||||
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.");
|
Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP.");
|
||||||
signedPreKeyRecord = KeyHelper.generateSignedPreKey(identityKeyPair, numSignedPreKeys + 1);
|
signedPreKeyRecord = KeyHelper.generateSignedPreKey(identityKeyPair, numSignedPreKeys + 1);
|
||||||
axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord);
|
axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord);
|
||||||
changed = true;
|
changed = true;
|
||||||
}
|
}
|
||||||
|
} catch (InvalidKeyIdException e) {
|
||||||
// Validate PreKeys
|
Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Adding new signedPreKey with ID " + (numSignedPreKeys + 1) + " to PEP.");
|
||||||
Set<PreKeyRecord> preKeyRecords = new HashSet<>();
|
signedPreKeyRecord = KeyHelper.generateSignedPreKey(identityKeyPair, numSignedPreKeys + 1);
|
||||||
if (keys != null) {
|
axolotlStore.storeSignedPreKey(signedPreKeyRecord.getId(), signedPreKeyRecord);
|
||||||
for (Integer id : keys.keySet()) {
|
changed = true;
|
||||||
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<PreKeyRecord> 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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate PreKeys
|
||||||
|
Set<PreKeyRecord> 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<PreKeyRecord> 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());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -669,44 +656,41 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
||||||
final boolean wipe,
|
final boolean wipe,
|
||||||
final boolean firstAttempt) {
|
final boolean firstAttempt) {
|
||||||
final Bundle publishOptions = account.getXmppConnection().getFeatures().pepPublishOptions() ? PublishOptions.openAccess() : null;
|
final Bundle publishOptions = account.getXmppConnection().getFeatures().pepPublishOptions() ? PublishOptions.openAccess() : null;
|
||||||
final IqPacket publish = mXmppConnectionService.getIqGenerator().publishBundles(
|
final Iq publish = mXmppConnectionService.getIqGenerator().publishBundles(
|
||||||
signedPreKeyRecord, axolotlStore.getIdentityKeyPair().getPublicKey(),
|
signedPreKeyRecord, axolotlStore.getIdentityKeyPair().getPublicKey(),
|
||||||
preKeyRecords, getOwnDeviceId(), publishOptions);
|
preKeyRecords, getOwnDeviceId(), publishOptions);
|
||||||
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ": Bundle " + getOwnDeviceId() + " in PEP not current. Publishing...");
|
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ": Bundle " + getOwnDeviceId() + " in PEP not current. Publishing...");
|
||||||
mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() {
|
mXmppConnectionService.sendIqPacket(account, publish, response -> {
|
||||||
@Override
|
final boolean preconditionNotMet = PublishOptions.preconditionNotMet(response);
|
||||||
public void onIqPacketReceived(final Account account, IqPacket packet) {
|
if (firstAttempt && preconditionNotMet) {
|
||||||
final boolean preconditionNotMet = PublishOptions.preconditionNotMet(packet);
|
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": precondition wasn't met for bundle. pushing node configuration");
|
||||||
if (firstAttempt && preconditionNotMet) {
|
final String node = AxolotlService.PEP_BUNDLES + ":" + getOwnDeviceId();
|
||||||
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": precondition wasn't met for bundle. pushing node configuration");
|
mXmppConnectionService.pushNodeConfiguration(account, node, publishOptions, new XmppConnectionService.OnConfigurationPushed() {
|
||||||
final String node = AxolotlService.PEP_BUNDLES + ":" + getOwnDeviceId();
|
@Override
|
||||||
mXmppConnectionService.pushNodeConfiguration(account, node, publishOptions, new XmppConnectionService.OnConfigurationPushed() {
|
public void onPushSucceeded() {
|
||||||
@Override
|
publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe, false);
|
||||||
public void onPushSucceeded() {
|
}
|
||||||
publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPushFailed() {
|
public void onPushFailed() {
|
||||||
publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe, false);
|
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) {
|
} else if (response.getType() == Iq.Type.RESULT) {
|
||||||
Log.d(Config.LOGTAG, getLogprefix(account) + "bundle precondition still not met after second attempt");
|
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Successfully published bundle. ");
|
||||||
} else {
|
if (wipe) {
|
||||||
Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing bundle: " + packet.toString());
|
wipeOtherPepDevices();
|
||||||
}
|
} else if (announceAfter) {
|
||||||
pepBroken = true;
|
Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId());
|
||||||
|
publishOwnDeviceIdIfNeeded();
|
||||||
}
|
}
|
||||||
|
} else if (response.getType() == Iq.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: " + response.toString());
|
||||||
|
}
|
||||||
|
pepBroken = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -759,9 +743,9 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
||||||
return Futures.immediateFuture(session);
|
return Futures.immediateFuture(session);
|
||||||
}
|
}
|
||||||
final SettableFuture<XmppAxolotlSession> future = SettableFuture.create();
|
final SettableFuture<XmppAxolotlSession> future = SettableFuture.create();
|
||||||
final IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveVerificationForDevice(jid, address.getDeviceId());
|
final Iq packet = mXmppConnectionService.getIqGenerator().retrieveVerificationForDevice(jid, address.getDeviceId());
|
||||||
mXmppConnectionService.sendIqPacket(account, packet, (account, response) -> {
|
mXmppConnectionService.sendIqPacket(account, packet, response -> {
|
||||||
Pair<X509Certificate[], byte[]> verification = mXmppConnectionService.getIqParser().verification(response);
|
Pair<X509Certificate[], byte[]> verification = IqParser.verification(response);
|
||||||
if (verification != null) {
|
if (verification != null) {
|
||||||
try {
|
try {
|
||||||
Signature verifier = Signature.getInstance("sha256WithRSA");
|
Signature verifier = Signature.getInstance("sha256WithRSA");
|
||||||
|
@ -846,7 +830,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fetchDeviceIds(final Jid jid, OnDeviceIdsFetched callback) {
|
private void fetchDeviceIds(final Jid jid, OnDeviceIdsFetched callback) {
|
||||||
IqPacket packet;
|
final Iq packet;
|
||||||
synchronized (this.fetchDeviceIdsMap) {
|
synchronized (this.fetchDeviceIdsMap) {
|
||||||
List<OnDeviceIdsFetched> callbacks = this.fetchDeviceIdsMap.get(jid);
|
List<OnDeviceIdsFetched> callbacks = this.fetchDeviceIdsMap.get(jid);
|
||||||
if (callbacks != null) {
|
if (callbacks != null) {
|
||||||
|
@ -866,11 +850,11 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (packet != null) {
|
if (packet != null) {
|
||||||
mXmppConnectionService.sendIqPacket(account, packet, (account, response) -> {
|
mXmppConnectionService.sendIqPacket(account, packet, response -> {
|
||||||
if (response.getType() == IqPacket.TYPE.RESULT) {
|
if (response.getType() == Iq.Type.RESULT) {
|
||||||
fetchDeviceListStatus.put(jid, true);
|
fetchDeviceListStatus.put(jid, true);
|
||||||
Element item = mXmppConnectionService.getIqParser().getItem(response);
|
final Element item = IqParser.getItem(response);
|
||||||
Set<Integer> deviceIds = mXmppConnectionService.getIqParser().deviceIds(item);
|
final Set<Integer> deviceIds = IqParser.deviceIds(item);
|
||||||
registerDevices(jid, deviceIds);
|
registerDevices(jid, deviceIds);
|
||||||
final List<OnDeviceIdsFetched> callbacks;
|
final List<OnDeviceIdsFetched> callbacks;
|
||||||
synchronized (fetchDeviceIdsMap) {
|
synchronized (fetchDeviceIdsMap) {
|
||||||
|
@ -882,7 +866,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (response.getType() == IqPacket.TYPE.TIMEOUT) {
|
if (response.getType() == Iq.Type.TIMEOUT) {
|
||||||
fetchDeviceListStatus.remove(jid);
|
fetchDeviceListStatus.remove(jid);
|
||||||
} else {
|
} else {
|
||||||
fetchDeviceListStatus.put(jid, false);
|
fetchDeviceListStatus.put(jid, false);
|
||||||
|
@ -929,16 +913,15 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
||||||
}
|
}
|
||||||
final Jid jid = Jid.of(address.getName());
|
final Jid jid = Jid.of(address.getName());
|
||||||
final boolean oneOfOurs = jid.asBareJid().equals(account.getJid().asBareJid());
|
final boolean oneOfOurs = jid.asBareJid().equals(account.getJid().asBareJid());
|
||||||
IqPacket bundlesPacket = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(jid, address.getDeviceId());
|
final Iq bundlesPacket = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(jid, address.getDeviceId());
|
||||||
mXmppConnectionService.sendIqPacket(account, bundlesPacket, (account, packet) -> {
|
mXmppConnectionService.sendIqPacket(account, bundlesPacket, packet -> {
|
||||||
if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
|
if (packet.getType() == Iq.Type.TIMEOUT) {
|
||||||
fetchStatusMap.put(address, FetchStatus.TIMEOUT);
|
fetchStatusMap.put(address, FetchStatus.TIMEOUT);
|
||||||
sessionSettableFuture.setException(new CryptoFailedException("Unable to build session. Timeout"));
|
sessionSettableFuture.setException(new CryptoFailedException("Unable to build session. Timeout"));
|
||||||
} else if (packet.getType() == IqPacket.TYPE.RESULT) {
|
} else if (packet.getType() == Iq.Type.RESULT) {
|
||||||
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received preKey IQ packet, processing...");
|
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received preKey IQ packet, processing...");
|
||||||
final IqParser parser = mXmppConnectionService.getIqParser();
|
final List<PreKeyBundle> preKeyBundleList = IqParser.preKeys(packet);
|
||||||
final List<PreKeyBundle> preKeyBundleList = parser.preKeys(packet);
|
final PreKeyBundle bundle = IqParser.bundle(packet);
|
||||||
final PreKeyBundle bundle = parser.bundle(packet);
|
|
||||||
if (preKeyBundleList.isEmpty() || bundle == null) {
|
if (preKeyBundleList.isEmpty() || bundle == null) {
|
||||||
Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "preKey IQ packet invalid: " + packet);
|
Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "preKey IQ packet invalid: " + packet);
|
||||||
fetchStatusMap.put(address, FetchStatus.ERROR);
|
fetchStatusMap.put(address, FetchStatus.ERROR);
|
||||||
|
@ -1544,7 +1527,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
||||||
axolotlMessage.addDevice(session, true);
|
axolotlMessage.addDevice(session, true);
|
||||||
try {
|
try {
|
||||||
final Jid jid = Jid.of(session.getRemoteAddress().getName());
|
final Jid jid = Jid.of(session.getRemoteAddress().getName());
|
||||||
MessagePacket packet = mXmppConnectionService.getMessageGenerator().generateKeyTransportMessage(jid, axolotlMessage);
|
final var packet = mXmppConnectionService.getMessageGenerator().generateKeyTransportMessage(jid, axolotlMessage);
|
||||||
mXmppConnectionService.sendMessagePacket(account, packet);
|
mXmppConnectionService.sendMessagePacket(account, packet);
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
throw new Error("Remote addresses are created from jid and should convert back to jid", e);
|
throw new Error("Remote addresses are created from jid and should convert back to jid", e);
|
||||||
|
|
|
@ -146,10 +146,10 @@ import eu.siacs.conversations.xmpp.chatstate.ChatState;
|
||||||
import eu.siacs.conversations.xmpp.forms.Data;
|
import eu.siacs.conversations.xmpp.forms.Data;
|
||||||
import eu.siacs.conversations.xmpp.forms.Option;
|
import eu.siacs.conversations.xmpp.forms.Option;
|
||||||
import eu.siacs.conversations.xmpp.mam.MamReference;
|
import eu.siacs.conversations.xmpp.mam.MamReference;
|
||||||
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
|
|
||||||
|
|
||||||
import static eu.siacs.conversations.entities.Bookmark.printableValue;
|
import static eu.siacs.conversations.entities.Bookmark.printableValue;
|
||||||
|
|
||||||
|
import im.conversations.android.xmpp.model.stanza.Iq;
|
||||||
|
|
||||||
public class Conversation extends AbstractEntity implements Blockable, Comparable<Conversation>, Conversational, AvatarService.Avatarable {
|
public class Conversation extends AbstractEntity implements Blockable, Comparable<Conversation>, Conversational, AvatarService.Avatarable {
|
||||||
public static final String TABLENAME = "conversations";
|
public static final String TABLENAME = "conversations";
|
||||||
|
@ -1600,7 +1600,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
||||||
show();
|
show();
|
||||||
CommandSession session = new CommandSession(command.getAttribute("name"), command.getAttribute("node"), xmppConnectionService);
|
CommandSession session = new CommandSession(command.getAttribute("name"), command.getAttribute("node"), xmppConnectionService);
|
||||||
|
|
||||||
final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
|
final var packet = new Iq(Iq.Type.SET);
|
||||||
packet.setTo(command.getAttributeAsJid("jid"));
|
packet.setTo(command.getAttributeAsJid("jid"));
|
||||||
final Element c = packet.addChild("command", Namespace.COMMANDS);
|
final Element c = packet.addChild("command", Namespace.COMMANDS);
|
||||||
c.setAttribute("node", command.getAttribute("node"));
|
c.setAttribute("node", command.getAttribute("node"));
|
||||||
|
@ -1618,7 +1618,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
||||||
}
|
}
|
||||||
}, 1000);
|
}, 1000);
|
||||||
} else {
|
} else {
|
||||||
xmppConnectionService.sendIqPacket(getAccount(), packet, (a, iq) -> {
|
xmppConnectionService.sendIqPacket(getAccount(), packet, (iq) -> {
|
||||||
session.updateWithResponse(iq);
|
session.updateWithResponse(iq);
|
||||||
}, 120L);
|
}, 120L);
|
||||||
}
|
}
|
||||||
|
@ -1645,7 +1645,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
||||||
|
|
||||||
public void startMucConfig(XmppConnectionService xmppConnectionService) {
|
public void startMucConfig(XmppConnectionService xmppConnectionService) {
|
||||||
MucConfigSession session = new MucConfigSession(xmppConnectionService);
|
MucConfigSession session = new MucConfigSession(xmppConnectionService);
|
||||||
final IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
|
final var packet = new Iq(Iq.Type.GET);
|
||||||
packet.setTo(Conversation.this.getJid().asBareJid());
|
packet.setTo(Conversation.this.getJid().asBareJid());
|
||||||
packet.addChild("query", "http://jabber.org/protocol/muc#owner");
|
packet.addChild("query", "http://jabber.org/protocol/muc#owner");
|
||||||
|
|
||||||
|
@ -1661,7 +1661,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
||||||
}
|
}
|
||||||
}, 1000);
|
}, 1000);
|
||||||
} else {
|
} else {
|
||||||
xmppConnectionService.sendIqPacket(getAccount(), packet, (a, iq) -> {
|
xmppConnectionService.sendIqPacket(getAccount(), packet, (iq) -> {
|
||||||
session.updateWithResponse(iq);
|
session.updateWithResponse(iq);
|
||||||
}, 120L);
|
}, 120L);
|
||||||
}
|
}
|
||||||
|
@ -2782,7 +2782,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
||||||
protected Item mkItem(Element el, int pos) {
|
protected Item mkItem(Element el, int pos) {
|
||||||
int viewType = TYPE_ERROR;
|
int viewType = TYPE_ERROR;
|
||||||
|
|
||||||
if (response != null && response.getType() == IqPacket.TYPE.RESULT) {
|
if (response != null && response.getType() == Iq.Type.RESULT) {
|
||||||
if (el.getName().equals("note")) {
|
if (el.getName().equals("note")) {
|
||||||
viewType = TYPE_NOTE;
|
viewType = TYPE_NOTE;
|
||||||
} else if (el.getNamespace().equals("jabber:x:oob")) {
|
} else if (el.getNamespace().equals("jabber:x:oob")) {
|
||||||
|
@ -2883,7 +2883,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
||||||
protected String mTitle;
|
protected String mTitle;
|
||||||
protected String mNode;
|
protected String mNode;
|
||||||
protected CommandPageBinding mBinding = null;
|
protected CommandPageBinding mBinding = null;
|
||||||
protected IqPacket response = null;
|
protected Iq response = null;
|
||||||
protected Element responseElement = null;
|
protected Element responseElement = null;
|
||||||
protected boolean expectingRemoval = false;
|
protected boolean expectingRemoval = false;
|
||||||
protected List<Field> reported = null;
|
protected List<Field> reported = null;
|
||||||
|
@ -2893,7 +2893,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
||||||
protected GridLayoutManager layoutManager;
|
protected GridLayoutManager layoutManager;
|
||||||
protected WebView actionToWebview = null;
|
protected WebView actionToWebview = null;
|
||||||
protected int fillableFieldCount = 0;
|
protected int fillableFieldCount = 0;
|
||||||
protected IqPacket pendingResponsePacket = null;
|
protected Iq pendingResponsePacket = null;
|
||||||
protected boolean waitingForRefresh = false;
|
protected boolean waitingForRefresh = false;
|
||||||
|
|
||||||
CommandSession(String title, String node, XmppConnectionService xmppConnectionService) {
|
CommandSession(String title, String node, XmppConnectionService xmppConnectionService) {
|
||||||
|
@ -2912,7 +2912,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
||||||
return mNode;
|
return mNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void updateWithResponse(final IqPacket iq) {
|
public void updateWithResponse(final Iq iq) {
|
||||||
if (getView() != null && getView().isAttachedToWindow()) {
|
if (getView() != null && getView().isAttachedToWindow()) {
|
||||||
getView().post(() -> updateWithResponseUiThread(iq));
|
getView().post(() -> updateWithResponseUiThread(iq));
|
||||||
} else {
|
} else {
|
||||||
|
@ -2920,7 +2920,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void updateWithResponseUiThread(final IqPacket iq) {
|
protected void updateWithResponseUiThread(final Iq iq) {
|
||||||
Timer oldTimer = this.loadingTimer;
|
Timer oldTimer = this.loadingTimer;
|
||||||
this.loadingTimer = new Timer();
|
this.loadingTimer = new Timer();
|
||||||
oldTimer.cancel();
|
oldTimer.cancel();
|
||||||
|
@ -2937,7 +2937,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
||||||
|
|
||||||
boolean actionsCleared = false;
|
boolean actionsCleared = false;
|
||||||
Element command = iq.findChild("command", "http://jabber.org/protocol/commands");
|
Element command = iq.findChild("command", "http://jabber.org/protocol/commands");
|
||||||
if (iq.getType() == IqPacket.TYPE.RESULT && command != null) {
|
if (iq.getType() == Iq.Type.RESULT && command != null) {
|
||||||
if (mNode.equals("jabber:iq:register") && command.getAttribute("status") != null && command.getAttribute("status").equals("completed")) {
|
if (mNode.equals("jabber:iq:register") && command.getAttribute("status") != null && command.getAttribute("status").equals("completed")) {
|
||||||
xmppConnectionService.createContact(getAccount().getRoster().getContact(iq.getFrom()), true);
|
xmppConnectionService.createContact(getAccount().getRoster().getContact(iq.getFrom()), true);
|
||||||
}
|
}
|
||||||
|
@ -3101,7 +3101,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
||||||
public int getItemCount() {
|
public int getItemCount() {
|
||||||
if (loading) return 1;
|
if (loading) return 1;
|
||||||
if (response == null) return 0;
|
if (response == null) return 0;
|
||||||
if (response.getType() == IqPacket.TYPE.RESULT && responseElement != null && responseElement.getNamespace().equals("jabber:x:data")) {
|
if (response.getType() == Iq.Type.RESULT && responseElement != null && responseElement.getNamespace().equals("jabber:x:data")) {
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for (Element el : responseElement.getChildren()) {
|
for (Element el : responseElement.getChildren()) {
|
||||||
if (!el.getNamespace().equals("jabber:x:data")) continue;
|
if (!el.getNamespace().equals("jabber:x:data")) continue;
|
||||||
|
@ -3134,7 +3134,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
||||||
if (items.get(position) != null) return items.get(position);
|
if (items.get(position) != null) return items.get(position);
|
||||||
if (response == null) return null;
|
if (response == null) return null;
|
||||||
|
|
||||||
if (response.getType() == IqPacket.TYPE.RESULT && responseElement != null) {
|
if (response.getType() == Iq.Type.RESULT && responseElement != null) {
|
||||||
if (responseElement.getNamespace().equals("jabber:x:data")) {
|
if (responseElement.getNamespace().equals("jabber:x:data")) {
|
||||||
int i = 0;
|
int i = 0;
|
||||||
for (Element el : responseElement.getChildren()) {
|
for (Element el : responseElement.getChildren()) {
|
||||||
|
@ -3317,7 +3317,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
|
final var packet = new Iq(Iq.Type.SET);
|
||||||
packet.setTo(response.getFrom());
|
packet.setTo(response.getFrom());
|
||||||
final Element c = packet.addChild("command", Namespace.COMMANDS);
|
final Element c = packet.addChild("command", Namespace.COMMANDS);
|
||||||
c.setAttribute("node", mNode);
|
c.setAttribute("node", mNode);
|
||||||
|
@ -3360,7 +3360,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
||||||
if (c.getAttribute("action") == null) c.setAttribute("action", action);
|
if (c.getAttribute("action") == null) c.setAttribute("action", action);
|
||||||
|
|
||||||
executing = true;
|
executing = true;
|
||||||
xmppConnectionService.sendIqPacket(getAccount(), packet, (a, iq) -> {
|
xmppConnectionService.sendIqPacket(getAccount(), packet, (iq) -> {
|
||||||
updateWithResponse(iq);
|
updateWithResponse(iq);
|
||||||
}, 120L);
|
}, 120L);
|
||||||
|
|
||||||
|
@ -3495,7 +3495,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
||||||
actionsAdapter.notifyDataSetChanged();
|
actionsAdapter.notifyDataSetChanged();
|
||||||
|
|
||||||
if (pendingResponsePacket != null) {
|
if (pendingResponsePacket != null) {
|
||||||
final IqPacket pending = pendingResponsePacket;
|
final var pending = pendingResponsePacket;
|
||||||
pendingResponsePacket = null;
|
pendingResponsePacket = null;
|
||||||
updateWithResponseUiThread(pending);
|
updateWithResponseUiThread(pending);
|
||||||
}
|
}
|
||||||
|
@ -3570,7 +3570,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void updateWithResponseUiThread(final IqPacket iq) {
|
protected void updateWithResponseUiThread(final Iq iq) {
|
||||||
Timer oldTimer = this.loadingTimer;
|
Timer oldTimer = this.loadingTimer;
|
||||||
this.loadingTimer = new Timer();
|
this.loadingTimer = new Timer();
|
||||||
oldTimer.cancel();
|
oldTimer.cancel();
|
||||||
|
@ -3586,7 +3586,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
||||||
layoutManager.setSpanCount(1);
|
layoutManager.setSpanCount(1);
|
||||||
|
|
||||||
final Element query = iq.findChild("query", "http://jabber.org/protocol/muc#owner");
|
final Element query = iq.findChild("query", "http://jabber.org/protocol/muc#owner");
|
||||||
if (iq.getType() == IqPacket.TYPE.RESULT && query != null) {
|
if (iq.getType() == Iq.Type.RESULT && query != null) {
|
||||||
final Data form = Data.parse(query.findChild("x", "jabber:x:data"));
|
final Data form = Data.parse(query.findChild("x", "jabber:x:data"));
|
||||||
final String title = form.getTitle();
|
final String title = form.getTitle();
|
||||||
if (title != null) {
|
if (title != null) {
|
||||||
|
@ -3605,7 +3605,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
||||||
if (actionsAdapter.getPosition("cancel") < 0) {
|
if (actionsAdapter.getPosition("cancel") < 0) {
|
||||||
actionsAdapter.insert(Pair.create("cancel", "cancel"), 0);
|
actionsAdapter.insert(Pair.create("cancel", "cancel"), 0);
|
||||||
}
|
}
|
||||||
} else if (iq.getType() == IqPacket.TYPE.RESULT) {
|
} else if (iq.getType() == Iq.Type.RESULT) {
|
||||||
expectingRemoval = true;
|
expectingRemoval = true;
|
||||||
removeSession(this);
|
removeSession(this);
|
||||||
return;
|
return;
|
||||||
|
@ -3619,7 +3619,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
||||||
@Override
|
@Override
|
||||||
public synchronized boolean execute(String action) {
|
public synchronized boolean execute(String action) {
|
||||||
if ("cancel".equals(action)) {
|
if ("cancel".equals(action)) {
|
||||||
final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
|
final var packet = new Iq(Iq.Type.SET);
|
||||||
packet.setTo(response.getFrom());
|
packet.setTo(response.getFrom());
|
||||||
final Element form = packet
|
final Element form = packet
|
||||||
.addChild("query", "http://jabber.org/protocol/muc#owner")
|
.addChild("query", "http://jabber.org/protocol/muc#owner")
|
||||||
|
@ -3631,7 +3631,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
||||||
|
|
||||||
if (!"save".equals(action)) return true;
|
if (!"save".equals(action)) return true;
|
||||||
|
|
||||||
final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
|
final var packet = new Iq(Iq.Type.SET);
|
||||||
packet.setTo(response.getFrom());
|
packet.setTo(response.getFrom());
|
||||||
|
|
||||||
String formType = responseElement == null ? null : responseElement.getAttribute("type");
|
String formType = responseElement == null ? null : responseElement.getAttribute("type");
|
||||||
|
@ -3647,7 +3647,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
||||||
}
|
}
|
||||||
|
|
||||||
executing = true;
|
executing = true;
|
||||||
xmppConnectionService.sendIqPacket(getAccount(), packet, (a, iq) -> {
|
xmppConnectionService.sendIqPacket(getAccount(), packet, (iq) -> {
|
||||||
updateWithResponse(iq);
|
updateWithResponse(iq);
|
||||||
}, 120L);
|
}, 120L);
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,14 @@ import eu.siacs.conversations.xmpp.forms.Field;
|
||||||
import eu.siacs.conversations.xmpp.pep.Avatar;
|
import eu.siacs.conversations.xmpp.pep.Avatar;
|
||||||
import eu.siacs.conversations.xml.Element;
|
import eu.siacs.conversations.xml.Element;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
public class MucOptions {
|
public class MucOptions {
|
||||||
|
|
||||||
public static final String STATUS_CODE_SELF_PRESENCE = "110";
|
public static final String STATUS_CODE_SELF_PRESENCE = "110";
|
||||||
|
@ -199,6 +207,11 @@ public class MucOptions {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean allowPmRaw() {
|
||||||
|
final Field field = getRoomInfoForm().getFieldByName("muc#roomconfig_allowpm");
|
||||||
|
return field == null || Arrays.asList("anyone","participants").contains(field.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
public boolean participating() {
|
public boolean participating() {
|
||||||
return self.getRole().ranks(Role.PARTICIPANT) || !moderated();
|
return self.getRole().ranks(Role.PARTICIPANT) || !moderated();
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,7 +24,7 @@ import eu.siacs.conversations.xml.Element;
|
||||||
import eu.siacs.conversations.xml.Namespace;
|
import eu.siacs.conversations.xml.Namespace;
|
||||||
import eu.siacs.conversations.xmpp.forms.Data;
|
import eu.siacs.conversations.xmpp.forms.Data;
|
||||||
import eu.siacs.conversations.xmpp.forms.Field;
|
import eu.siacs.conversations.xmpp.forms.Field;
|
||||||
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
|
import im.conversations.android.xmpp.model.stanza.Iq;
|
||||||
|
|
||||||
public class ServiceDiscoveryResult {
|
public class ServiceDiscoveryResult {
|
||||||
public static final String TABLENAME = "discovery_results";
|
public static final String TABLENAME = "discovery_results";
|
||||||
|
@ -36,7 +36,7 @@ public class ServiceDiscoveryResult {
|
||||||
protected final List<String> features;
|
protected final List<String> features;
|
||||||
protected final List<Data> forms;
|
protected final List<Data> forms;
|
||||||
private final List<Identity> identities;
|
private final List<Identity> identities;
|
||||||
public ServiceDiscoveryResult(final IqPacket packet) {
|
public ServiceDiscoveryResult(final Iq packet) {
|
||||||
this.identities = new ArrayList<>();
|
this.identities = new ArrayList<>();
|
||||||
this.features = new ArrayList<>();
|
this.features = new ArrayList<>();
|
||||||
this.forms = new ArrayList<>();
|
this.forms = new ArrayList<>();
|
||||||
|
@ -279,7 +279,7 @@ public class ServiceDiscoveryResult {
|
||||||
return values;
|
return values;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Identity implements Comparable {
|
public static class Identity implements Comparable<Identity> {
|
||||||
protected final String type;
|
protected final String type;
|
||||||
protected final String lang;
|
protected final String lang;
|
||||||
protected final String name;
|
protected final String name;
|
||||||
|
@ -327,8 +327,21 @@ public class ServiceDiscoveryResult {
|
||||||
return this.name;
|
return this.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int compareTo(@NonNull Object other) {
|
JSONObject toJSON() {
|
||||||
Identity o = (Identity) other;
|
try {
|
||||||
|
JSONObject o = new JSONObject();
|
||||||
|
o.put("category", this.getCategory());
|
||||||
|
o.put("type", this.getType());
|
||||||
|
o.put("lang", this.getLang());
|
||||||
|
o.put("name", this.getName());
|
||||||
|
return o;
|
||||||
|
} catch (JSONException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(final Identity o) {
|
||||||
int r = blankNull(this.getCategory()).compareTo(blankNull(o.getCategory()));
|
int r = blankNull(this.getCategory()).compareTo(blankNull(o.getCategory()));
|
||||||
if (r == 0) {
|
if (r == 0) {
|
||||||
r = blankNull(this.getType()).compareTo(blankNull(o.getType()));
|
r = blankNull(this.getType()).compareTo(blankNull(o.getType()));
|
||||||
|
@ -342,18 +355,5 @@ public class ServiceDiscoveryResult {
|
||||||
|
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
JSONObject toJSON() {
|
|
||||||
try {
|
|
||||||
JSONObject o = new JSONObject();
|
|
||||||
o.put("category", this.getCategory());
|
|
||||||
o.put("type", this.getType());
|
|
||||||
o.put("lang", this.getLang());
|
|
||||||
o.put("name", this.getName());
|
|
||||||
return o;
|
|
||||||
} catch (JSONException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,7 +45,7 @@ import eu.siacs.conversations.xml.Namespace;
|
||||||
import eu.siacs.conversations.xmpp.Jid;
|
import eu.siacs.conversations.xmpp.Jid;
|
||||||
import eu.siacs.conversations.xmpp.forms.Data;
|
import eu.siacs.conversations.xmpp.forms.Data;
|
||||||
import eu.siacs.conversations.xmpp.pep.Avatar;
|
import eu.siacs.conversations.xmpp.pep.Avatar;
|
||||||
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
|
import im.conversations.android.xmpp.model.stanza.Iq;
|
||||||
|
|
||||||
public class IqGenerator extends AbstractGenerator {
|
public class IqGenerator extends AbstractGenerator {
|
||||||
|
|
||||||
|
@ -53,8 +53,8 @@ public class IqGenerator extends AbstractGenerator {
|
||||||
super(service);
|
super(service);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IqPacket discoResponse(final Account account, final IqPacket request) {
|
public Iq discoResponse(final Account account, final Iq request) {
|
||||||
final IqPacket packet = new IqPacket(IqPacket.TYPE.RESULT);
|
final var packet = new Iq(Iq.Type.RESULT);
|
||||||
packet.setId(request.getId());
|
packet.setId(request.getId());
|
||||||
packet.setTo(request.getFrom());
|
packet.setTo(request.getFrom());
|
||||||
final Element query = packet.addChild("query", "http://jabber.org/protocol/disco#info");
|
final Element query = packet.addChild("query", "http://jabber.org/protocol/disco#info");
|
||||||
|
@ -69,8 +69,8 @@ public class IqGenerator extends AbstractGenerator {
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IqPacket versionResponse(final IqPacket request) {
|
public Iq versionResponse(final Iq request) {
|
||||||
final IqPacket packet = request.generateResponse(IqPacket.TYPE.RESULT);
|
final var packet = request.generateResponse(Iq.Type.RESULT);
|
||||||
Element query = packet.query("jabber:iq:version");
|
Element query = packet.query("jabber:iq:version");
|
||||||
query.addChild("name").setContent(mXmppConnectionService.getString(R.string.app_name));
|
query.addChild("name").setContent(mXmppConnectionService.getString(R.string.app_name));
|
||||||
query.addChild("version").setContent(getIdentityVersion());
|
query.addChild("version").setContent(getIdentityVersion());
|
||||||
|
@ -93,8 +93,8 @@ public class IqGenerator extends AbstractGenerator {
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IqPacket entityTimeResponse(IqPacket request) {
|
public Iq entityTimeResponse(final Iq request) {
|
||||||
final IqPacket packet = request.generateResponse(IqPacket.TYPE.RESULT);
|
final Iq packet = request.generateResponse(Iq.Type.RESULT);
|
||||||
Element time = packet.addChild("time", "urn:xmpp:time");
|
Element time = packet.addChild("time", "urn:xmpp:time");
|
||||||
final long now = System.currentTimeMillis();
|
final long now = System.currentTimeMillis();
|
||||||
time.addChild("utc").setContent(getTimestamp(now));
|
time.addChild("utc").setContent(getTimestamp(now));
|
||||||
|
@ -113,14 +113,14 @@ public class IqGenerator extends AbstractGenerator {
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IqPacket purgeOfflineMessages() {
|
public static Iq purgeOfflineMessages() {
|
||||||
final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
|
final Iq packet = new Iq(Iq.Type.SET);
|
||||||
packet.addChild("offline", Namespace.FLEXIBLE_OFFLINE_MESSAGE_RETRIEVAL).addChild("purge");
|
packet.addChild("offline", Namespace.FLEXIBLE_OFFLINE_MESSAGE_RETRIEVAL).addChild("purge");
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected IqPacket publish(final String node, final Element item, final Bundle options) {
|
protected Iq publish(final String node, final Element item, final Bundle options) {
|
||||||
final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
|
final var packet = new Iq(Iq.Type.SET);
|
||||||
final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB);
|
final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB);
|
||||||
final Element publish = pubsub.addChild("publish");
|
final Element publish = pubsub.addChild("publish");
|
||||||
publish.setAttribute("node", node);
|
publish.setAttribute("node", node);
|
||||||
|
@ -132,12 +132,12 @@ public class IqGenerator extends AbstractGenerator {
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected IqPacket publish(final String node, final Element item) {
|
protected Iq publish(final String node, final Element item) {
|
||||||
return publish(node, item, null);
|
return publish(node, item, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private IqPacket retrieve(String node, Element item) {
|
private Iq retrieve(String node, Element item) {
|
||||||
final IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
|
final var packet = new Iq(Iq.Type.GET);
|
||||||
final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB);
|
final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB);
|
||||||
final Element items = pubsub.addChild("items");
|
final Element items = pubsub.addChild("items");
|
||||||
items.setAttribute("node", node);
|
items.setAttribute("node", node);
|
||||||
|
@ -147,36 +147,36 @@ public class IqGenerator extends AbstractGenerator {
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IqPacket retrieveVcard4(final Jid jid) {
|
public Iq retrieveVcard4(final Jid jid) {
|
||||||
final IqPacket packet = retrieve("urn:xmpp:vcard4", null);
|
final var packet = retrieve("urn:xmpp:vcard4", null);
|
||||||
packet.setTo(jid);
|
packet.setTo(jid);
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IqPacket retrieveBookmarks() {
|
public Iq retrieveBookmarks() {
|
||||||
return retrieve(Namespace.BOOKMARKS2, null);
|
return retrieve(Namespace.BOOKMARKS2, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IqPacket retrieveMds() {
|
public Iq retrieveMds() {
|
||||||
return retrieve(Namespace.MDS_DISPLAYED, null);
|
return retrieve(Namespace.MDS_DISPLAYED, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IqPacket publishNick(String nick) {
|
public Iq publishNick(String nick) {
|
||||||
final Element item = new Element("item");
|
final Element item = new Element("item");
|
||||||
item.setAttribute("id", "current");
|
item.setAttribute("id", "current");
|
||||||
item.addChild("nick", Namespace.NICK).setContent(nick);
|
item.addChild("nick", Namespace.NICK).setContent(nick);
|
||||||
return publish(Namespace.NICK, item);
|
return publish(Namespace.NICK, item);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IqPacket deleteNode(final String node) {
|
public Iq deleteNode(final String node) {
|
||||||
final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
|
final var packet = new Iq(Iq.Type.SET);
|
||||||
final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB_OWNER);
|
final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB_OWNER);
|
||||||
pubsub.addChild("delete").setAttribute("node", node);
|
pubsub.addChild("delete").setAttribute("node", node);
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IqPacket deleteItem(final String node, final String id) {
|
public Iq deleteItem(final String node, final String id) {
|
||||||
IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
|
final var packet = new Iq(Iq.Type.SET);
|
||||||
final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB);
|
final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB);
|
||||||
final Element retract = pubsub.addChild("retract");
|
final Element retract = pubsub.addChild("retract");
|
||||||
retract.setAttribute("node", node);
|
retract.setAttribute("node", node);
|
||||||
|
@ -185,7 +185,7 @@ public class IqGenerator extends AbstractGenerator {
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IqPacket publishAvatar(Avatar avatar, Bundle options) {
|
public Iq publishAvatar(Avatar avatar, Bundle options) {
|
||||||
final Element item = new Element("item");
|
final Element item = new Element("item");
|
||||||
item.setAttribute("id", avatar.sha1sum);
|
item.setAttribute("id", avatar.sha1sum);
|
||||||
final Element data = item.addChild("data", Namespace.AVATAR_DATA);
|
final Element data = item.addChild("data", Namespace.AVATAR_DATA);
|
||||||
|
@ -193,14 +193,14 @@ public class IqGenerator extends AbstractGenerator {
|
||||||
return publish(Namespace.AVATAR_DATA, item, options);
|
return publish(Namespace.AVATAR_DATA, item, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IqPacket publishElement(final String namespace, final Element element, String id, final Bundle options) {
|
public Iq publishElement(final String namespace, final Element element, String id, final Bundle options) {
|
||||||
final Element item = new Element("item");
|
final Element item = new Element("item");
|
||||||
item.setAttribute("id", id);
|
item.setAttribute("id", id);
|
||||||
item.addChild(element);
|
item.addChild(element);
|
||||||
return publish(namespace, item, options);
|
return publish(namespace, item, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IqPacket publishAvatarMetadata(final Avatar avatar, final Bundle options) {
|
public Iq publishAvatarMetadata(final Avatar avatar, final Bundle options) {
|
||||||
final Element item = new Element("item");
|
final Element item = new Element("item");
|
||||||
item.setAttribute("id", avatar.sha1sum);
|
item.setAttribute("id", avatar.sha1sum);
|
||||||
final Element metadata = item
|
final Element metadata = item
|
||||||
|
@ -214,57 +214,57 @@ public class IqGenerator extends AbstractGenerator {
|
||||||
return publish(Namespace.AVATAR_METADATA, item, options);
|
return publish(Namespace.AVATAR_METADATA, item, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IqPacket retrievePepAvatar(final Avatar avatar) {
|
public Iq retrievePepAvatar(final Avatar avatar) {
|
||||||
final Element item = new Element("item");
|
final Element item = new Element("item");
|
||||||
item.setAttribute("id", avatar.sha1sum);
|
item.setAttribute("id", avatar.sha1sum);
|
||||||
final IqPacket packet = retrieve(Namespace.AVATAR_DATA, item);
|
final var packet = retrieve(Namespace.AVATAR_DATA, item);
|
||||||
packet.setTo(avatar.owner);
|
packet.setTo(avatar.owner);
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IqPacket retrieveVcardAvatar(final Avatar avatar) {
|
public Iq retrieveVcardAvatar(final Avatar avatar) {
|
||||||
final IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
|
final Iq packet = new Iq(Iq.Type.GET);
|
||||||
packet.setTo(avatar.owner);
|
packet.setTo(avatar.owner);
|
||||||
packet.addChild("vCard", "vcard-temp");
|
packet.addChild("vCard", "vcard-temp");
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IqPacket retrieveVcardAvatar(final Jid to) {
|
public Iq retrieveVcardAvatar(final Jid to) {
|
||||||
final IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
|
final Iq packet = new Iq(Iq.Type.GET);
|
||||||
packet.setTo(to);
|
packet.setTo(to);
|
||||||
packet.addChild("vCard", "vcard-temp");
|
packet.addChild("vCard", "vcard-temp");
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IqPacket retrieveAvatarMetaData(final Jid to) {
|
public Iq retrieveAvatarMetaData(final Jid to) {
|
||||||
final IqPacket packet = retrieve("urn:xmpp:avatar:metadata", null);
|
final Iq packet = retrieve("urn:xmpp:avatar:metadata", null);
|
||||||
if (to != null) {
|
if (to != null) {
|
||||||
packet.setTo(to);
|
packet.setTo(to);
|
||||||
}
|
}
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IqPacket retrieveDeviceIds(final Jid to) {
|
public Iq retrieveDeviceIds(final Jid to) {
|
||||||
final IqPacket packet = retrieve(AxolotlService.PEP_DEVICE_LIST, null);
|
final var packet = retrieve(AxolotlService.PEP_DEVICE_LIST, null);
|
||||||
if (to != null) {
|
if (to != null) {
|
||||||
packet.setTo(to);
|
packet.setTo(to);
|
||||||
}
|
}
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IqPacket retrieveBundlesForDevice(final Jid to, final int deviceid) {
|
public Iq retrieveBundlesForDevice(final Jid to, final int deviceid) {
|
||||||
final IqPacket packet = retrieve(AxolotlService.PEP_BUNDLES + ":" + deviceid, null);
|
final var packet = retrieve(AxolotlService.PEP_BUNDLES + ":" + deviceid, null);
|
||||||
packet.setTo(to);
|
packet.setTo(to);
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IqPacket retrieveVerificationForDevice(final Jid to, final int deviceid) {
|
public Iq retrieveVerificationForDevice(final Jid to, final int deviceid) {
|
||||||
final IqPacket packet = retrieve(AxolotlService.PEP_VERIFICATION + ":" + deviceid, null);
|
final var packet = retrieve(AxolotlService.PEP_VERIFICATION + ":" + deviceid, null);
|
||||||
packet.setTo(to);
|
packet.setTo(to);
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IqPacket publishDeviceIds(final Set<Integer> ids, final Bundle publishOptions) {
|
public Iq publishDeviceIds(final Set<Integer> ids, final Bundle publishOptions) {
|
||||||
final Element item = new Element("item");
|
final Element item = new Element("item");
|
||||||
item.setAttribute("id", "current");
|
item.setAttribute("id", "current");
|
||||||
final Element list = item.addChild("list", AxolotlService.PEP_PREFIX);
|
final Element list = item.addChild("list", AxolotlService.PEP_PREFIX);
|
||||||
|
@ -314,7 +314,7 @@ public class IqGenerator extends AbstractGenerator {
|
||||||
return displayed;
|
return displayed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IqPacket publishBundles(final SignedPreKeyRecord signedPreKeyRecord, final IdentityKey identityKey,
|
public Iq publishBundles(final SignedPreKeyRecord signedPreKeyRecord, final IdentityKey identityKey,
|
||||||
final Set<PreKeyRecord> preKeyRecords, final int deviceId, Bundle publishOptions) {
|
final Set<PreKeyRecord> preKeyRecords, final int deviceId, Bundle publishOptions) {
|
||||||
final Element item = new Element("item");
|
final Element item = new Element("item");
|
||||||
item.setAttribute("id", "current");
|
item.setAttribute("id", "current");
|
||||||
|
@ -338,7 +338,7 @@ public class IqGenerator extends AbstractGenerator {
|
||||||
return publish(AxolotlService.PEP_BUNDLES + ":" + deviceId, item, publishOptions);
|
return publish(AxolotlService.PEP_BUNDLES + ":" + deviceId, item, publishOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IqPacket publishVerification(byte[] signature, X509Certificate[] certificates, final int deviceId) {
|
public Iq publishVerification(byte[] signature, X509Certificate[] certificates, final int deviceId) {
|
||||||
final Element item = new Element("item");
|
final Element item = new Element("item");
|
||||||
item.setAttribute("id", "current");
|
item.setAttribute("id", "current");
|
||||||
final Element verification = item.addChild("verification", AxolotlService.PEP_PREFIX);
|
final Element verification = item.addChild("verification", AxolotlService.PEP_PREFIX);
|
||||||
|
@ -356,8 +356,8 @@ public class IqGenerator extends AbstractGenerator {
|
||||||
return publish(AxolotlService.PEP_VERIFICATION + ":" + deviceId, item);
|
return publish(AxolotlService.PEP_VERIFICATION + ":" + deviceId, item);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IqPacket queryMessageArchiveManagement(final MessageArchiveService.Query mam) {
|
public Iq queryMessageArchiveManagement(final MessageArchiveService.Query mam) {
|
||||||
final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
|
final Iq packet = new Iq(Iq.Type.SET);
|
||||||
final Element query = packet.query(mam.version.namespace);
|
final Element query = packet.query(mam.version.namespace);
|
||||||
query.setAttribute("queryid", mam.getQueryId());
|
query.setAttribute("queryid", mam.getQueryId());
|
||||||
final Data data = new Data();
|
final Data data = new Data();
|
||||||
|
@ -387,15 +387,15 @@ public class IqGenerator extends AbstractGenerator {
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IqPacket generateGetBlockList() {
|
public Iq generateGetBlockList() {
|
||||||
final IqPacket iq = new IqPacket(IqPacket.TYPE.GET);
|
final Iq iq = new Iq(Iq.Type.GET);
|
||||||
iq.addChild("blocklist", Namespace.BLOCKING);
|
iq.addChild("blocklist", Namespace.BLOCKING);
|
||||||
|
|
||||||
return iq;
|
return iq;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IqPacket generateSetBlockRequest(final Jid jid, final boolean reportSpam, final String serverMsgId) {
|
public Iq generateSetBlockRequest(final Jid jid, final boolean reportSpam, final String serverMsgId) {
|
||||||
final IqPacket iq = new IqPacket(IqPacket.TYPE.SET);
|
final Iq iq = new Iq(Iq.Type.SET);
|
||||||
final Element block = iq.addChild("block", Namespace.BLOCKING);
|
final Element block = iq.addChild("block", Namespace.BLOCKING);
|
||||||
final Element item = block.addChild("item").setAttribute("jid", jid);
|
final Element item = block.addChild("item").setAttribute("jid", jid);
|
||||||
if (reportSpam) {
|
if (reportSpam) {
|
||||||
|
@ -411,15 +411,15 @@ public class IqGenerator extends AbstractGenerator {
|
||||||
return iq;
|
return iq;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IqPacket generateSetUnblockRequest(final Jid jid) {
|
public Iq generateSetUnblockRequest(final Jid jid) {
|
||||||
final IqPacket iq = new IqPacket(IqPacket.TYPE.SET);
|
final Iq iq = new Iq(Iq.Type.SET);
|
||||||
final Element block = iq.addChild("unblock", Namespace.BLOCKING);
|
final Element block = iq.addChild("unblock", Namespace.BLOCKING);
|
||||||
block.addChild("item").setAttribute("jid", jid);
|
block.addChild("item").setAttribute("jid", jid);
|
||||||
return iq;
|
return iq;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IqPacket generateSetPassword(final Account account, final String newPassword) {
|
public Iq generateSetPassword(final Account account, final String newPassword) {
|
||||||
final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
|
final Iq packet = new Iq(Iq.Type.SET);
|
||||||
packet.setTo(account.getDomain());
|
packet.setTo(account.getDomain());
|
||||||
final Element query = packet.addChild("query", Namespace.REGISTER);
|
final Element query = packet.addChild("query", Namespace.REGISTER);
|
||||||
final Jid jid = account.getJid();
|
final Jid jid = account.getJid();
|
||||||
|
@ -428,14 +428,14 @@ public class IqGenerator extends AbstractGenerator {
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IqPacket changeAffiliation(Conversation conference, Jid jid, String affiliation) {
|
public Iq changeAffiliation(Conversation conference, Jid jid, String affiliation) {
|
||||||
List<Jid> jids = new ArrayList<>();
|
List<Jid> jids = new ArrayList<>();
|
||||||
jids.add(jid);
|
jids.add(jid);
|
||||||
return changeAffiliation(conference, jids, affiliation);
|
return changeAffiliation(conference, jids, affiliation);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IqPacket changeAffiliation(Conversation conference, List<Jid> jids, String affiliation) {
|
public Iq changeAffiliation(Conversation conference, List<Jid> jids, String affiliation) {
|
||||||
IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
|
final Iq packet = new Iq(Iq.Type.SET);
|
||||||
packet.setTo(conference.getJid().asBareJid());
|
packet.setTo(conference.getJid().asBareJid());
|
||||||
packet.setFrom(conference.getAccount().getJid());
|
packet.setFrom(conference.getAccount().getJid());
|
||||||
Element query = packet.query("http://jabber.org/protocol/muc#admin");
|
Element query = packet.query("http://jabber.org/protocol/muc#admin");
|
||||||
|
@ -447,8 +447,8 @@ public class IqGenerator extends AbstractGenerator {
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IqPacket changeRole(Conversation conference, String nick, String role) {
|
public Iq changeRole(Conversation conference, String nick, String role) {
|
||||||
IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
|
final Iq packet = new Iq(Iq.Type.SET);
|
||||||
packet.setTo(conference.getJid().asBareJid());
|
packet.setTo(conference.getJid().asBareJid());
|
||||||
packet.setFrom(conference.getAccount().getJid());
|
packet.setFrom(conference.getAccount().getJid());
|
||||||
Element item = packet.query("http://jabber.org/protocol/muc#admin").addChild("item");
|
Element item = packet.query("http://jabber.org/protocol/muc#admin").addChild("item");
|
||||||
|
@ -457,11 +457,11 @@ public class IqGenerator extends AbstractGenerator {
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IqPacket moderateMessage(Account account, Message m, String reason) {
|
public Iq moderateMessage(Account account, Message m, String reason) {
|
||||||
IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
|
final var packet = new Iq(Iq.Type.SET);
|
||||||
packet.setTo(m.getConversation().getJid().asBareJid());
|
packet.setTo(m.getConversation().getJid().asBareJid());
|
||||||
packet.setFrom(account.getJid());
|
packet.setFrom(account.getJid());
|
||||||
Element moderate =
|
final var moderate =
|
||||||
packet.addChild("apply-to", "urn:xmpp:fasten:0")
|
packet.addChild("apply-to", "urn:xmpp:fasten:0")
|
||||||
.setAttribute("id", m.getServerMsgId())
|
.setAttribute("id", m.getServerMsgId())
|
||||||
.addChild("moderate", "urn:xmpp:message-moderate:0");
|
.addChild("moderate", "urn:xmpp:message-moderate:0");
|
||||||
|
@ -470,8 +470,8 @@ public class IqGenerator extends AbstractGenerator {
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IqPacket requestHttpUploadSlot(Jid host, DownloadableFile file, String name, String mime) {
|
public Iq requestHttpUploadSlot(Jid host, DownloadableFile file, String name, String mime) {
|
||||||
IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
|
final Iq packet = new Iq(Iq.Type.GET);
|
||||||
packet.setTo(host);
|
packet.setTo(host);
|
||||||
Element request = packet.addChild("request", Namespace.HTTP_UPLOAD);
|
Element request = packet.addChild("request", Namespace.HTTP_UPLOAD);
|
||||||
request.setAttribute("filename", name == null ? convertFilename(file.getName()) : name);
|
request.setAttribute("filename", name == null ? convertFilename(file.getName()) : name);
|
||||||
|
@ -480,8 +480,8 @@ public class IqGenerator extends AbstractGenerator {
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IqPacket requestHttpUploadLegacySlot(Jid host, DownloadableFile file, String mime) {
|
public Iq requestHttpUploadLegacySlot(Jid host, DownloadableFile file, String mime) {
|
||||||
IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
|
final Iq packet = new Iq(Iq.Type.GET);
|
||||||
packet.setTo(host);
|
packet.setTo(host);
|
||||||
Element request = packet.addChild("request", Namespace.HTTP_UPLOAD_LEGACY);
|
Element request = packet.addChild("request", Namespace.HTTP_UPLOAD_LEGACY);
|
||||||
request.addChild("filename").setContent(convertFilename(file.getName()));
|
request.addChild("filename").setContent(convertFilename(file.getName()));
|
||||||
|
@ -507,8 +507,8 @@ public class IqGenerator extends AbstractGenerator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public IqPacket generateCreateAccountWithCaptcha(Account account, String id, Data data) {
|
public static Iq generateCreateAccountWithCaptcha(final Account account, final String id, final Data data) {
|
||||||
final IqPacket register = new IqPacket(IqPacket.TYPE.SET);
|
final Iq register = new Iq(Iq.Type.SET);
|
||||||
register.setFrom(account.getJid().asBareJid());
|
register.setFrom(account.getJid().asBareJid());
|
||||||
register.setTo(account.getDomain());
|
register.setTo(account.getDomain());
|
||||||
register.setId(id);
|
register.setId(id);
|
||||||
|
@ -519,12 +519,12 @@ public class IqGenerator extends AbstractGenerator {
|
||||||
return register;
|
return register;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IqPacket pushTokenToAppServer(Jid appServer, String token, String deviceId) {
|
public Iq pushTokenToAppServer(Jid appServer, String token, String deviceId) {
|
||||||
return pushTokenToAppServer(appServer, token, deviceId, null);
|
return pushTokenToAppServer(appServer, token, deviceId, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IqPacket pushTokenToAppServer(Jid appServer, String token, String deviceId, Jid muc) {
|
public Iq pushTokenToAppServer(Jid appServer, String token, String deviceId, Jid muc) {
|
||||||
final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
|
final Iq packet = new Iq(Iq.Type.SET);
|
||||||
packet.setTo(appServer);
|
packet.setTo(appServer);
|
||||||
final Element command = packet.addChild("command", Namespace.COMMANDS);
|
final Element command = packet.addChild("command", Namespace.COMMANDS);
|
||||||
command.setAttribute("node", "register-push-fcm");
|
command.setAttribute("node", "register-push-fcm");
|
||||||
|
@ -540,8 +540,8 @@ public class IqGenerator extends AbstractGenerator {
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IqPacket unregisterChannelOnAppServer(Jid appServer, String deviceId, String channel) {
|
public Iq unregisterChannelOnAppServer(Jid appServer, String deviceId, String channel) {
|
||||||
final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
|
final Iq packet = new Iq(Iq.Type.SET);
|
||||||
packet.setTo(appServer);
|
packet.setTo(appServer);
|
||||||
final Element command = packet.addChild("command", Namespace.COMMANDS);
|
final Element command = packet.addChild("command", Namespace.COMMANDS);
|
||||||
command.setAttribute("node", "unregister-push-fcm");
|
command.setAttribute("node", "unregister-push-fcm");
|
||||||
|
@ -554,8 +554,8 @@ public class IqGenerator extends AbstractGenerator {
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IqPacket enablePush(final Jid jid, final String node, final String secret) {
|
public Iq enablePush(final Jid jid, final String node, final String secret) {
|
||||||
IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
|
final Iq packet = new Iq(Iq.Type.SET);
|
||||||
Element enable = packet.addChild("enable", Namespace.PUSH);
|
Element enable = packet.addChild("enable", Namespace.PUSH);
|
||||||
enable.setAttribute("jid", jid);
|
enable.setAttribute("jid", jid);
|
||||||
enable.setAttribute("node", node);
|
enable.setAttribute("node", node);
|
||||||
|
@ -569,16 +569,16 @@ public class IqGenerator extends AbstractGenerator {
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IqPacket disablePush(final Jid jid, final String node) {
|
public Iq disablePush(final Jid jid, final String node) {
|
||||||
IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
|
Iq packet = new Iq(Iq.Type.SET);
|
||||||
Element disable = packet.addChild("disable", Namespace.PUSH);
|
Element disable = packet.addChild("disable", Namespace.PUSH);
|
||||||
disable.setAttribute("jid", jid);
|
disable.setAttribute("jid", jid);
|
||||||
disable.setAttribute("node", node);
|
disable.setAttribute("node", node);
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IqPacket queryAffiliation(Conversation conversation, String affiliation) {
|
public Iq queryAffiliation(Conversation conversation, String affiliation) {
|
||||||
IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
|
final Iq packet = new Iq(Iq.Type.GET);
|
||||||
packet.setTo(conversation.getJid().asBareJid());
|
packet.setTo(conversation.getJid().asBareJid());
|
||||||
packet.query("http://jabber.org/protocol/muc#admin").addChild("item").setAttribute("affiliation", affiliation);
|
packet.query("http://jabber.org/protocol/muc#admin").addChild("item").setAttribute("affiliation", affiliation);
|
||||||
return packet;
|
return packet;
|
||||||
|
@ -611,16 +611,16 @@ public class IqGenerator extends AbstractGenerator {
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IqPacket requestPubsubConfiguration(Jid jid, String node) {
|
public Iq requestPubsubConfiguration(Jid jid, String node) {
|
||||||
return pubsubConfiguration(jid, node, null);
|
return pubsubConfiguration(jid, node, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IqPacket publishPubsubConfiguration(Jid jid, String node, Data data) {
|
public Iq publishPubsubConfiguration(Jid jid, String node, Data data) {
|
||||||
return pubsubConfiguration(jid, node, data);
|
return pubsubConfiguration(jid, node, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
private IqPacket pubsubConfiguration(Jid jid, String node, Data data) {
|
private Iq pubsubConfiguration(Jid jid, String node, Data data) {
|
||||||
IqPacket packet = new IqPacket(data == null ? IqPacket.TYPE.GET : IqPacket.TYPE.SET);
|
final Iq packet = new Iq(data == null ? Iq.Type.GET : Iq.Type.SET);
|
||||||
packet.setTo(jid);
|
packet.setTo(jid);
|
||||||
Element pubsub = packet.addChild("pubsub", "http://jabber.org/protocol/pubsub#owner");
|
Element pubsub = packet.addChild("pubsub", "http://jabber.org/protocol/pubsub#owner");
|
||||||
Element configure = pubsub.addChild("configure").setAttribute("node", node);
|
Element configure = pubsub.addChild("configure").setAttribute("node", node);
|
||||||
|
@ -630,43 +630,43 @@ public class IqGenerator extends AbstractGenerator {
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IqPacket queryDiscoItems(Jid jid) {
|
public Iq queryDiscoItems(final Jid jid) {
|
||||||
IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
|
final Iq packet = new Iq(Iq.Type.GET);
|
||||||
packet.setTo(jid);
|
packet.setTo(jid);
|
||||||
packet.query(Namespace.DISCO_ITEMS);
|
packet.query(Namespace.DISCO_ITEMS);
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IqPacket queryDiscoItems(Jid jid, String node) {
|
public Iq queryDiscoItems(Jid jid, String node) {
|
||||||
IqPacket packet = queryDiscoItems(jid);
|
final var packet = queryDiscoItems(jid);
|
||||||
final Element query = packet.query(Namespace.DISCO_ITEMS);
|
final var query = packet.query(Namespace.DISCO_ITEMS);
|
||||||
query.setAttribute("node", node);
|
query.setAttribute("node", node);
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IqPacket queryDiscoInfo(Jid jid) {
|
public Iq queryDiscoInfo(final Jid jid) {
|
||||||
IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
|
final Iq packet = new Iq(Iq.Type.GET);
|
||||||
packet.setTo(jid);
|
packet.setTo(jid);
|
||||||
packet.addChild("query",Namespace.DISCO_INFO);
|
packet.addChild("query",Namespace.DISCO_INFO);
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IqPacket bobResponse(IqPacket request) {
|
public Iq bobResponse(Iq request) {
|
||||||
try {
|
try {
|
||||||
String bobCid = request.findChild("data", "urn:xmpp:bob").getAttribute("cid");
|
final var bobCid = request.findChild("data", "urn:xmpp:bob").getAttribute("cid");
|
||||||
Cid cid = BobTransfer.cid(bobCid);
|
final var cid = BobTransfer.cid(bobCid);
|
||||||
DownloadableFile f = mXmppConnectionService.getFileForCid(cid);
|
final var f = mXmppConnectionService.getFileForCid(cid);
|
||||||
if (f == null || !f.canRead()) {
|
if (f == null || !f.canRead()) {
|
||||||
throw new IOException("No such file");
|
throw new IOException("No such file");
|
||||||
} else if (f.getSize() > 129000) {
|
} else if (f.getSize() > 129000) {
|
||||||
final IqPacket response = request.generateResponse(IqPacket.TYPE.ERROR);
|
final var response = request.generateResponse(Iq.Type.ERROR);
|
||||||
final Element error = response.addChild("error");
|
final var error = response.addChild("error");
|
||||||
error.setAttribute("type", "cancel");
|
error.setAttribute("type", "cancel");
|
||||||
error.addChild("policy-violation", "urn:ietf:params:xml:ns:xmpp-stanzas");
|
error.addChild("policy-violation", "urn:ietf:params:xml:ns:xmpp-stanzas");
|
||||||
return response;
|
return response;
|
||||||
} else {
|
} else {
|
||||||
final IqPacket response = request.generateResponse(IqPacket.TYPE.RESULT);
|
final var response = request.generateResponse(Iq.Type.RESULT);
|
||||||
final Element data = response.addChild("data", "urn:xmpp:bob");
|
final var data = response.addChild("data", "urn:xmpp:bob");
|
||||||
data.setAttribute("cid", bobCid);
|
data.setAttribute("cid", bobCid);
|
||||||
data.setAttribute("type", f.getMimeType());
|
data.setAttribute("type", f.getMimeType());
|
||||||
ByteArrayOutputStream b64 = new ByteArrayOutputStream((int) f.getSize() * 2);
|
ByteArrayOutputStream b64 = new ByteArrayOutputStream((int) f.getSize() * 2);
|
||||||
|
@ -678,8 +678,8 @@ public class IqGenerator extends AbstractGenerator {
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
} catch (final IOException | IllegalStateException e) {
|
} catch (final IOException | IllegalStateException e) {
|
||||||
final IqPacket response = request.generateResponse(IqPacket.TYPE.ERROR);
|
final var response = request.generateResponse(Iq.Type.ERROR);
|
||||||
final Element error = response.addChild("error");
|
final var error = response.addChild("error");
|
||||||
error.setAttribute("type", "cancel");
|
error.setAttribute("type", "cancel");
|
||||||
error.addChild("item-not-found", "urn:ietf:params:xml:ns:xmpp-stanzas");
|
error.addChild("item-not-found", "urn:ietf:params:xml:ns:xmpp-stanzas");
|
||||||
return response;
|
return response;
|
||||||
|
|
|
@ -23,7 +23,6 @@ import eu.siacs.conversations.xmpp.jingle.JingleConnectionManager;
|
||||||
import eu.siacs.conversations.xmpp.jingle.JingleRtpConnection;
|
import eu.siacs.conversations.xmpp.jingle.JingleRtpConnection;
|
||||||
import eu.siacs.conversations.xmpp.jingle.Media;
|
import eu.siacs.conversations.xmpp.jingle.Media;
|
||||||
import eu.siacs.conversations.xmpp.jingle.stanzas.Reason;
|
import eu.siacs.conversations.xmpp.jingle.stanzas.Reason;
|
||||||
import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
|
|
||||||
|
|
||||||
public class MessageGenerator extends AbstractGenerator {
|
public class MessageGenerator extends AbstractGenerator {
|
||||||
private static final String OMEMO_FALLBACK_MESSAGE = "I sent you an OMEMO encrypted message but your client doesn’t seem to support that. Find more information on https://conversations.im/omemo";
|
private static final String OMEMO_FALLBACK_MESSAGE = "I sent you an OMEMO encrypted message but your client doesn’t seem to support that. Find more information on https://conversations.im/omemo";
|
||||||
|
@ -33,25 +32,25 @@ public class MessageGenerator extends AbstractGenerator {
|
||||||
super(service);
|
super(service);
|
||||||
}
|
}
|
||||||
|
|
||||||
private MessagePacket preparePacket(Message message, boolean legacyEncryption) {
|
private im.conversations.android.xmpp.model.stanza.Message preparePacket(Message message, boolean legacyEncryption) {
|
||||||
Conversation conversation = (Conversation) message.getConversation();
|
Conversation conversation = (Conversation) message.getConversation();
|
||||||
Account account = conversation.getAccount();
|
Account account = conversation.getAccount();
|
||||||
MessagePacket packet = new MessagePacket();
|
im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message();
|
||||||
final boolean isWithSelf = conversation.getContact().isSelf();
|
final boolean isWithSelf = conversation.getContact().isSelf();
|
||||||
if (conversation.getMode() == Conversation.MODE_SINGLE) {
|
if (conversation.getMode() == Conversation.MODE_SINGLE) {
|
||||||
packet.setTo(message.getCounterpart());
|
packet.setTo(message.getCounterpart());
|
||||||
packet.setType(MessagePacket.TYPE_CHAT);
|
packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.CHAT);
|
||||||
if (!isWithSelf) {
|
if (!isWithSelf) {
|
||||||
packet.addChild("request", "urn:xmpp:receipts");
|
packet.addChild("request", "urn:xmpp:receipts");
|
||||||
}
|
}
|
||||||
} else if (message.isPrivateMessage()) {
|
} else if (message.isPrivateMessage()) {
|
||||||
packet.setTo(message.getCounterpart());
|
packet.setTo(message.getCounterpart());
|
||||||
packet.setType(MessagePacket.TYPE_CHAT);
|
packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.CHAT);
|
||||||
packet.addChild("x", "http://jabber.org/protocol/muc#user");
|
packet.addChild("x", "http://jabber.org/protocol/muc#user");
|
||||||
packet.addChild("request", "urn:xmpp:receipts");
|
packet.addChild("request", "urn:xmpp:receipts");
|
||||||
} else {
|
} else {
|
||||||
packet.setTo(message.getCounterpart().asBareJid());
|
packet.setTo(message.getCounterpart().asBareJid());
|
||||||
packet.setType(MessagePacket.TYPE_GROUPCHAT);
|
packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.GROUPCHAT);
|
||||||
}
|
}
|
||||||
if (conversation.isSingleOrPrivateAndNonAnonymous() && !message.isPrivateMessage()) {
|
if (conversation.isSingleOrPrivateAndNonAnonymous() && !message.isPrivateMessage()) {
|
||||||
packet.addChild("markable", "urn:xmpp:chat-markers:0");
|
packet.addChild("markable", "urn:xmpp:chat-markers:0");
|
||||||
|
@ -78,7 +77,7 @@ public class MessageGenerator extends AbstractGenerator {
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addDelay(MessagePacket packet, long timestamp) {
|
public void addDelay(im.conversations.android.xmpp.model.stanza.Message packet, long timestamp) {
|
||||||
final SimpleDateFormat mDateFormat = new SimpleDateFormat(
|
final SimpleDateFormat mDateFormat = new SimpleDateFormat(
|
||||||
"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US);
|
"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US);
|
||||||
mDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
|
mDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||||
|
@ -87,8 +86,8 @@ public class MessageGenerator extends AbstractGenerator {
|
||||||
delay.setAttribute("stamp", mDateFormat.format(date));
|
delay.setAttribute("stamp", mDateFormat.format(date));
|
||||||
}
|
}
|
||||||
|
|
||||||
public MessagePacket generateAxolotlChat(Message message, XmppAxolotlMessage axolotlMessage) {
|
public im.conversations.android.xmpp.model.stanza.Message generateAxolotlChat(Message message, XmppAxolotlMessage axolotlMessage) {
|
||||||
MessagePacket packet = preparePacket(message, true);
|
im.conversations.android.xmpp.model.stanza.Message packet = preparePacket(message, true);
|
||||||
if (axolotlMessage == null) {
|
if (axolotlMessage == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
@ -101,17 +100,18 @@ public class MessageGenerator extends AbstractGenerator {
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MessagePacket generateKeyTransportMessage(Jid to, XmppAxolotlMessage axolotlMessage) {
|
public im.conversations.android.xmpp.model.stanza.Message generateKeyTransportMessage(Jid to, XmppAxolotlMessage axolotlMessage) {
|
||||||
MessagePacket packet = new MessagePacket();
|
im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message();
|
||||||
packet.setType(MessagePacket.TYPE_CHAT);
|
packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.CHAT);
|
||||||
packet.setTo(to);
|
packet.setTo(to);
|
||||||
packet.setAxolotlMessage(axolotlMessage.toElement());
|
packet.setAxolotlMessage(axolotlMessage.toElement());
|
||||||
packet.addChild("store", "urn:xmpp:hints");
|
packet.addChild("store", "urn:xmpp:hints");
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MessagePacket generateChat(Message message) {
|
public im.conversations.android.xmpp.model.stanza.Message generateChat(Message message) {
|
||||||
MessagePacket packet = preparePacket(message, false);
|
im.conversations.android.xmpp.model.stanza.Message packet = preparePacket(message, false);
|
||||||
|
String content;
|
||||||
if (message.hasFileOnRemoteHost()) {
|
if (message.hasFileOnRemoteHost()) {
|
||||||
final Message.FileParams fileParams = message.getFileParams();
|
final Message.FileParams fileParams = message.getFileParams();
|
||||||
|
|
||||||
|
@ -139,8 +139,8 @@ public class MessageGenerator extends AbstractGenerator {
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MessagePacket generatePgpChat(Message message) {
|
public im.conversations.android.xmpp.model.stanza.Message generatePgpChat(Message message) {
|
||||||
MessagePacket packet = preparePacket(message, true);
|
final im.conversations.android.xmpp.model.stanza.Message packet = preparePacket(message, true);
|
||||||
if (message.hasFileOnRemoteHost()) {
|
if (message.hasFileOnRemoteHost()) {
|
||||||
Message.FileParams fileParams = message.getFileParams();
|
Message.FileParams fileParams = message.getFileParams();
|
||||||
final String url = fileParams.url;
|
final String url = fileParams.url;
|
||||||
|
@ -163,10 +163,10 @@ public class MessageGenerator extends AbstractGenerator {
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MessagePacket generateChatState(Conversation conversation) {
|
public im.conversations.android.xmpp.model.stanza.Message generateChatState(Conversation conversation) {
|
||||||
final Account account = conversation.getAccount();
|
final Account account = conversation.getAccount();
|
||||||
MessagePacket packet = new MessagePacket();
|
final im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message();
|
||||||
packet.setType(conversation.getMode() == Conversation.MODE_MULTI ? MessagePacket.TYPE_GROUPCHAT : MessagePacket.TYPE_CHAT);
|
packet.setType(conversation.getMode() == Conversation.MODE_MULTI ? im.conversations.android.xmpp.model.stanza.Message.Type.GROUPCHAT : im.conversations.android.xmpp.model.stanza.Message.Type.CHAT);
|
||||||
packet.setTo(conversation.getJid().asBareJid());
|
packet.setTo(conversation.getJid().asBareJid());
|
||||||
packet.setFrom(account.getJid());
|
packet.setFrom(account.getJid());
|
||||||
packet.addChild(ChatState.toElement(conversation.getOutgoingChatState()));
|
packet.addChild(ChatState.toElement(conversation.getOutgoingChatState()));
|
||||||
|
@ -175,11 +175,11 @@ public class MessageGenerator extends AbstractGenerator {
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MessagePacket confirm(final Message message) {
|
public im.conversations.android.xmpp.model.stanza.Message confirm(final Message message) {
|
||||||
final boolean groupChat = message.getConversation().getMode() == Conversational.MODE_MULTI;
|
final boolean groupChat = message.getConversation().getMode() == Conversational.MODE_MULTI;
|
||||||
final Jid to = message.getCounterpart();
|
final Jid to = message.getCounterpart();
|
||||||
final MessagePacket packet = new MessagePacket();
|
final im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message();
|
||||||
packet.setType(groupChat ? MessagePacket.TYPE_GROUPCHAT : MessagePacket.TYPE_CHAT);
|
packet.setType(groupChat ? im.conversations.android.xmpp.model.stanza.Message.Type.GROUPCHAT : im.conversations.android.xmpp.model.stanza.Message.Type.CHAT);
|
||||||
packet.setTo(groupChat ? to.asBareJid() : to);
|
packet.setTo(groupChat ? to.asBareJid() : to);
|
||||||
final Element displayed = packet.addChild("displayed", "urn:xmpp:chat-markers:0");
|
final Element displayed = packet.addChild("displayed", "urn:xmpp:chat-markers:0");
|
||||||
if (groupChat) {
|
if (groupChat) {
|
||||||
|
@ -197,20 +197,20 @@ public class MessageGenerator extends AbstractGenerator {
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MessagePacket conferenceSubject(Conversation conversation, String subject) {
|
public im.conversations.android.xmpp.model.stanza.Message conferenceSubject(Conversation conversation, String subject) {
|
||||||
MessagePacket packet = new MessagePacket();
|
im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message();
|
||||||
packet.setType(MessagePacket.TYPE_GROUPCHAT);
|
packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.GROUPCHAT);
|
||||||
packet.setTo(conversation.getJid().asBareJid());
|
packet.setTo(conversation.getJid().asBareJid());
|
||||||
packet.addChild("subject").setContent(subject);
|
packet.addChild("subject").setContent(subject);
|
||||||
packet.setFrom(conversation.getAccount().getJid().asBareJid());
|
packet.setFrom(conversation.getAccount().getJid().asBareJid());
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MessagePacket requestVoice(Jid jid) {
|
public im.conversations.android.xmpp.model.stanza.Message requestVoice(Jid jid) {
|
||||||
MessagePacket packet = new MessagePacket();
|
final var packet = new im.conversations.android.xmpp.model.stanza.Message();
|
||||||
packet.setType(MessagePacket.TYPE_NORMAL);
|
packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.NORMAL);
|
||||||
packet.setTo(jid.asBareJid());
|
packet.setTo(jid.asBareJid());
|
||||||
Data form = new Data();
|
final var form = new Data();
|
||||||
form.setFormType("http://jabber.org/protocol/muc#request");
|
form.setFormType("http://jabber.org/protocol/muc#request");
|
||||||
form.put("muc#role", "participant");
|
form.put("muc#role", "participant");
|
||||||
form.submit();
|
form.submit();
|
||||||
|
@ -218,9 +218,9 @@ public class MessageGenerator extends AbstractGenerator {
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MessagePacket directInvite(final Conversation conversation, final Jid contact) {
|
public im.conversations.android.xmpp.model.stanza.Message directInvite(final Conversation conversation, final Jid contact) {
|
||||||
MessagePacket packet = new MessagePacket();
|
im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message();
|
||||||
packet.setType(MessagePacket.TYPE_NORMAL);
|
packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.NORMAL);
|
||||||
packet.setTo(contact);
|
packet.setTo(contact);
|
||||||
packet.setFrom(conversation.getAccount().getJid());
|
packet.setFrom(conversation.getAccount().getJid());
|
||||||
Element x = packet.addChild("x", "jabber:x:conference");
|
Element x = packet.addChild("x", "jabber:x:conference");
|
||||||
|
@ -236,8 +236,8 @@ public class MessageGenerator extends AbstractGenerator {
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MessagePacket invite(final Conversation conversation, final Jid contact) {
|
public im.conversations.android.xmpp.model.stanza.Message invite(final Conversation conversation, final Jid contact) {
|
||||||
final MessagePacket packet = new MessagePacket();
|
final var packet = new im.conversations.android.xmpp.model.stanza.Message();
|
||||||
packet.setTo(conversation.getJid().asBareJid());
|
packet.setTo(conversation.getJid().asBareJid());
|
||||||
packet.setFrom(conversation.getAccount().getJid());
|
packet.setFrom(conversation.getAccount().getJid());
|
||||||
Element x = new Element("x");
|
Element x = new Element("x");
|
||||||
|
@ -249,8 +249,9 @@ public class MessageGenerator extends AbstractGenerator {
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MessagePacket received(Account account, final Jid from, final String id, ArrayList<String> namespaces, int type) {
|
public im.conversations.android.xmpp.model.stanza.Message received(Account account, final Jid from, final String id, ArrayList<String> namespaces, im.conversations.android.xmpp.model.stanza.Message.Type type) {
|
||||||
final MessagePacket receivedPacket = new MessagePacket();
|
final var receivedPacket =
|
||||||
|
new im.conversations.android.xmpp.model.stanza.Message();
|
||||||
receivedPacket.setType(type);
|
receivedPacket.setType(type);
|
||||||
receivedPacket.setTo(from);
|
receivedPacket.setTo(from);
|
||||||
receivedPacket.setFrom(account.getJid());
|
receivedPacket.setFrom(account.getJid());
|
||||||
|
@ -261,8 +262,8 @@ public class MessageGenerator extends AbstractGenerator {
|
||||||
return receivedPacket;
|
return receivedPacket;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MessagePacket received(Account account, Jid to, String id) {
|
public im.conversations.android.xmpp.model.stanza.Message received(Account account, Jid to, String id) {
|
||||||
MessagePacket packet = new MessagePacket();
|
im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message();
|
||||||
packet.setFrom(account.getJid());
|
packet.setFrom(account.getJid());
|
||||||
packet.setTo(to);
|
packet.setTo(to);
|
||||||
packet.addChild("received", "urn:xmpp:receipts").setAttribute("id", id);
|
packet.addChild("received", "urn:xmpp:receipts").setAttribute("id", id);
|
||||||
|
@ -270,10 +271,10 @@ public class MessageGenerator extends AbstractGenerator {
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MessagePacket sessionFinish(
|
public im.conversations.android.xmpp.model.stanza.Message sessionFinish(
|
||||||
final Jid with, final String sessionId, final Reason reason) {
|
final Jid with, final String sessionId, final Reason reason) {
|
||||||
final MessagePacket packet = new MessagePacket();
|
final im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message();
|
||||||
packet.setType(MessagePacket.TYPE_CHAT);
|
packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.CHAT);
|
||||||
packet.setTo(with);
|
packet.setTo(with);
|
||||||
final Element finish = packet.addChild("finish", Namespace.JINGLE_MESSAGE);
|
final Element finish = packet.addChild("finish", Namespace.JINGLE_MESSAGE);
|
||||||
finish.setAttribute("id", sessionId);
|
finish.setAttribute("id", sessionId);
|
||||||
|
@ -283,9 +284,9 @@ public class MessageGenerator extends AbstractGenerator {
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MessagePacket sessionProposal(final JingleConnectionManager.RtpSessionProposal proposal) {
|
public im.conversations.android.xmpp.model.stanza.Message sessionProposal(final JingleConnectionManager.RtpSessionProposal proposal) {
|
||||||
final MessagePacket packet = new MessagePacket();
|
final im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message();
|
||||||
packet.setType(MessagePacket.TYPE_CHAT); //we want to carbon copy those
|
packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.CHAT); //we want to carbon copy those
|
||||||
packet.setTo(proposal.with);
|
packet.setTo(proposal.with);
|
||||||
packet.setId(JingleRtpConnection.JINGLE_MESSAGE_PROPOSE_ID_PREFIX + proposal.sessionId);
|
packet.setId(JingleRtpConnection.JINGLE_MESSAGE_PROPOSE_ID_PREFIX + proposal.sessionId);
|
||||||
final Element propose = packet.addChild("propose", Namespace.JINGLE_MESSAGE);
|
final Element propose = packet.addChild("propose", Namespace.JINGLE_MESSAGE);
|
||||||
|
@ -298,9 +299,9 @@ public class MessageGenerator extends AbstractGenerator {
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MessagePacket sessionRetract(final JingleConnectionManager.RtpSessionProposal proposal) {
|
public im.conversations.android.xmpp.model.stanza.Message sessionRetract(final JingleConnectionManager.RtpSessionProposal proposal) {
|
||||||
final MessagePacket packet = new MessagePacket();
|
final im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message();
|
||||||
packet.setType(MessagePacket.TYPE_CHAT); //we want to carbon copy those
|
packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.CHAT); //we want to carbon copy those
|
||||||
packet.setTo(proposal.with);
|
packet.setTo(proposal.with);
|
||||||
final Element propose = packet.addChild("retract", Namespace.JINGLE_MESSAGE);
|
final Element propose = packet.addChild("retract", Namespace.JINGLE_MESSAGE);
|
||||||
propose.setAttribute("id", proposal.sessionId);
|
propose.setAttribute("id", proposal.sessionId);
|
||||||
|
@ -309,9 +310,9 @@ public class MessageGenerator extends AbstractGenerator {
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MessagePacket sessionReject(final Jid with, final String sessionId) {
|
public im.conversations.android.xmpp.model.stanza.Message sessionReject(final Jid with, final String sessionId) {
|
||||||
final MessagePacket packet = new MessagePacket();
|
final im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message();
|
||||||
packet.setType(MessagePacket.TYPE_CHAT); //we want to carbon copy those
|
packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.CHAT); //we want to carbon copy those
|
||||||
packet.setTo(with);
|
packet.setTo(with);
|
||||||
final Element propose = packet.addChild("reject", Namespace.JINGLE_MESSAGE);
|
final Element propose = packet.addChild("reject", Namespace.JINGLE_MESSAGE);
|
||||||
propose.setAttribute("id", sessionId);
|
propose.setAttribute("id", sessionId);
|
||||||
|
|
|
@ -9,7 +9,6 @@ import eu.siacs.conversations.entities.Presence;
|
||||||
import eu.siacs.conversations.services.XmppConnectionService;
|
import eu.siacs.conversations.services.XmppConnectionService;
|
||||||
import eu.siacs.conversations.xml.Element;
|
import eu.siacs.conversations.xml.Element;
|
||||||
import eu.siacs.conversations.xml.Namespace;
|
import eu.siacs.conversations.xml.Namespace;
|
||||||
import eu.siacs.conversations.xmpp.stanzas.PresencePacket;
|
|
||||||
|
|
||||||
public class PresenceGenerator extends AbstractGenerator {
|
public class PresenceGenerator extends AbstractGenerator {
|
||||||
|
|
||||||
|
@ -17,20 +16,20 @@ public class PresenceGenerator extends AbstractGenerator {
|
||||||
super(service);
|
super(service);
|
||||||
}
|
}
|
||||||
|
|
||||||
private PresencePacket subscription(String type, Contact contact) {
|
private im.conversations.android.xmpp.model.stanza.Presence subscription(String type, Contact contact) {
|
||||||
PresencePacket packet = new PresencePacket();
|
im.conversations.android.xmpp.model.stanza.Presence packet = new im.conversations.android.xmpp.model.stanza.Presence();
|
||||||
packet.setAttribute("type", type);
|
packet.setAttribute("type", type);
|
||||||
packet.setTo(contact.getJid());
|
packet.setTo(contact.getJid());
|
||||||
packet.setFrom(contact.getAccount().getJid().asBareJid());
|
packet.setFrom(contact.getAccount().getJid().asBareJid());
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PresencePacket requestPresenceUpdatesFrom(final Contact contact) {
|
public im.conversations.android.xmpp.model.stanza.Presence requestPresenceUpdatesFrom(final Contact contact) {
|
||||||
return requestPresenceUpdatesFrom(contact, null);
|
return requestPresenceUpdatesFrom(contact, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PresencePacket requestPresenceUpdatesFrom(final Contact contact, final String preAuth) {
|
public im.conversations.android.xmpp.model.stanza.Presence requestPresenceUpdatesFrom(final Contact contact, final String preAuth) {
|
||||||
PresencePacket packet = subscription("subscribe", contact);
|
im.conversations.android.xmpp.model.stanza.Presence packet = subscription("subscribe", contact);
|
||||||
String displayName = contact.getAccount().getDisplayName();
|
String displayName = contact.getAccount().getDisplayName();
|
||||||
if (!TextUtils.isEmpty(displayName)) {
|
if (!TextUtils.isEmpty(displayName)) {
|
||||||
packet.addChild("nick", Namespace.NICK).setContent(displayName);
|
packet.addChild("nick", Namespace.NICK).setContent(displayName);
|
||||||
|
@ -41,24 +40,24 @@ public class PresenceGenerator extends AbstractGenerator {
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PresencePacket stopPresenceUpdatesFrom(Contact contact) {
|
public im.conversations.android.xmpp.model.stanza.Presence stopPresenceUpdatesFrom(Contact contact) {
|
||||||
return subscription("unsubscribe", contact);
|
return subscription("unsubscribe", contact);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PresencePacket stopPresenceUpdatesTo(Contact contact) {
|
public im.conversations.android.xmpp.model.stanza.Presence stopPresenceUpdatesTo(Contact contact) {
|
||||||
return subscription("unsubscribed", contact);
|
return subscription("unsubscribed", contact);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PresencePacket sendPresenceUpdatesTo(Contact contact) {
|
public im.conversations.android.xmpp.model.stanza.Presence sendPresenceUpdatesTo(Contact contact) {
|
||||||
return subscription("subscribed", contact);
|
return subscription("subscribed", contact);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PresencePacket selfPresence(Account account, Presence.Status status) {
|
public im.conversations.android.xmpp.model.stanza.Presence selfPresence(Account account, Presence.Status status) {
|
||||||
return selfPresence(account, status, true, null);
|
return selfPresence(account, status, true, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public PresencePacket selfPresence(final Account account, final Presence.Status status, final boolean personal, final String nickname) {
|
public im.conversations.android.xmpp.model.stanza.Presence selfPresence(final Account account, final Presence.Status status, final boolean personal, final String nickname) {
|
||||||
final PresencePacket packet = new PresencePacket();
|
final im.conversations.android.xmpp.model.stanza.Presence packet = new im.conversations.android.xmpp.model.stanza.Presence();
|
||||||
if (personal) {
|
if (personal) {
|
||||||
final String sig = account.getPgpSignature();
|
final String sig = account.getPgpSignature();
|
||||||
final String message = account.getPresenceStatusMessage();
|
final String message = account.getPresenceStatusMessage();
|
||||||
|
@ -87,16 +86,16 @@ public class PresenceGenerator extends AbstractGenerator {
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PresencePacket leave(final MucOptions mucOptions) {
|
public im.conversations.android.xmpp.model.stanza.Presence leave(final MucOptions mucOptions) {
|
||||||
PresencePacket presencePacket = new PresencePacket();
|
im.conversations.android.xmpp.model.stanza.Presence presence = new im.conversations.android.xmpp.model.stanza.Presence();
|
||||||
presencePacket.setTo(mucOptions.getSelf().getFullJid());
|
presence.setTo(mucOptions.getSelf().getFullJid());
|
||||||
presencePacket.setFrom(mucOptions.getAccount().getJid());
|
presence.setFrom(mucOptions.getAccount().getJid());
|
||||||
presencePacket.setAttribute("type", "unavailable");
|
presence.setAttribute("type", "unavailable");
|
||||||
return presencePacket;
|
return presence;
|
||||||
}
|
}
|
||||||
|
|
||||||
public PresencePacket sendOfflinePresence(Account account) {
|
public im.conversations.android.xmpp.model.stanza.Presence sendOfflinePresence(Account account) {
|
||||||
PresencePacket packet = new PresencePacket();
|
im.conversations.android.xmpp.model.stanza.Presence packet = new im.conversations.android.xmpp.model.stanza.Presence();
|
||||||
packet.setFrom(account.getJid());
|
packet.setFrom(account.getJid());
|
||||||
packet.setAttribute("type", "unavailable");
|
packet.setAttribute("type", "unavailable");
|
||||||
return packet;
|
return packet;
|
||||||
|
|
|
@ -2,11 +2,27 @@ package eu.siacs.conversations.http;
|
||||||
|
|
||||||
import static eu.siacs.conversations.utils.Random.SECURE_RANDOM;
|
import static eu.siacs.conversations.utils.Random.SECURE_RANDOM;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.core.util.Consumer;
|
import androidx.core.util.Consumer;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.BuildConfig;
|
||||||
|
import eu.siacs.conversations.Config;
|
||||||
|
import eu.siacs.conversations.crypto.TrustManagers;
|
||||||
|
import eu.siacs.conversations.entities.Account;
|
||||||
|
import eu.siacs.conversations.entities.DownloadableFile;
|
||||||
|
import eu.siacs.conversations.entities.Message;
|
||||||
|
import eu.siacs.conversations.services.AbstractConnectionManager;
|
||||||
|
import eu.siacs.conversations.services.XmppConnectionService;
|
||||||
|
import eu.siacs.conversations.utils.TLSSocketFactory;
|
||||||
|
|
||||||
|
import okhttp3.HttpUrl;
|
||||||
|
import okhttp3.OkHttpClient;
|
||||||
|
import okhttp3.Request;
|
||||||
|
import okhttp3.ResponseBody;
|
||||||
|
|
||||||
import org.apache.http.conn.ssl.StrictHostnameVerifier;
|
import org.apache.http.conn.ssl.StrictHostnameVerifier;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -16,7 +32,9 @@ import java.net.InetSocketAddress;
|
||||||
import java.net.Proxy;
|
import java.net.Proxy;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.security.KeyManagementException;
|
import java.security.KeyManagementException;
|
||||||
|
import java.security.KeyStoreException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.cert.CertificateException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
@ -26,19 +44,6 @@ import java.util.concurrent.TimeUnit;
|
||||||
import javax.net.ssl.SSLSocketFactory;
|
import javax.net.ssl.SSLSocketFactory;
|
||||||
import javax.net.ssl.X509TrustManager;
|
import javax.net.ssl.X509TrustManager;
|
||||||
|
|
||||||
import eu.siacs.conversations.BuildConfig;
|
|
||||||
import eu.siacs.conversations.Config;
|
|
||||||
import eu.siacs.conversations.entities.Account;
|
|
||||||
import eu.siacs.conversations.entities.DownloadableFile;
|
|
||||||
import eu.siacs.conversations.entities.Message;
|
|
||||||
import eu.siacs.conversations.services.AbstractConnectionManager;
|
|
||||||
import eu.siacs.conversations.services.XmppConnectionService;
|
|
||||||
import eu.siacs.conversations.utils.TLSSocketFactory;
|
|
||||||
import okhttp3.HttpUrl;
|
|
||||||
import okhttp3.OkHttpClient;
|
|
||||||
import okhttp3.Request;
|
|
||||||
import okhttp3.ResponseBody;
|
|
||||||
|
|
||||||
public class HttpConnectionManager extends AbstractConnectionManager {
|
public class HttpConnectionManager extends AbstractConnectionManager {
|
||||||
|
|
||||||
private final List<HttpDownloadConnection> downloadConnections = new ArrayList<>();
|
private final List<HttpDownloadConnection> downloadConnections = new ArrayList<>();
|
||||||
|
@ -46,7 +51,7 @@ public class HttpConnectionManager extends AbstractConnectionManager {
|
||||||
|
|
||||||
public static final Executor EXECUTOR = Executors.newFixedThreadPool(4);
|
public static final Executor EXECUTOR = Executors.newFixedThreadPool(4);
|
||||||
|
|
||||||
public static final OkHttpClient OK_HTTP_CLIENT;
|
private static final OkHttpClient OK_HTTP_CLIENT;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
OK_HTTP_CLIENT = new OkHttpClient.Builder()
|
OK_HTTP_CLIENT = new OkHttpClient.Builder()
|
||||||
|
@ -209,4 +214,27 @@ public class HttpConnectionManager extends AbstractConnectionManager {
|
||||||
|
|
||||||
return filename;
|
return filename;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static OkHttpClient okHttpClient(final Context context) {
|
||||||
|
final OkHttpClient.Builder builder = HttpConnectionManager.OK_HTTP_CLIENT.newBuilder();
|
||||||
|
try {
|
||||||
|
final X509TrustManager trustManager;
|
||||||
|
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N) {
|
||||||
|
trustManager = TrustManagers.defaultWithBundledLetsEncrypt(context);
|
||||||
|
} else {
|
||||||
|
trustManager = TrustManagers.createDefaultTrustManager();
|
||||||
|
}
|
||||||
|
final SSLSocketFactory socketFactory =
|
||||||
|
new TLSSocketFactory(new X509TrustManager[] {trustManager}, SECURE_RANDOM);
|
||||||
|
builder.sslSocketFactory(socketFactory, trustManager);
|
||||||
|
} catch (final IOException
|
||||||
|
| KeyManagementException
|
||||||
|
| NoSuchAlgorithmException
|
||||||
|
| KeyStoreException
|
||||||
|
| CertificateException e) {
|
||||||
|
Log.d(Config.LOGTAG, "not reconfiguring service to work with bundled LetsEncrypt");
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,7 +43,7 @@ import eu.siacs.conversations.xml.Element;
|
||||||
import eu.siacs.conversations.xml.Namespace;
|
import eu.siacs.conversations.xml.Namespace;
|
||||||
import eu.siacs.conversations.xmpp.IqResponseException;
|
import eu.siacs.conversations.xmpp.IqResponseException;
|
||||||
import eu.siacs.conversations.xmpp.Jid;
|
import eu.siacs.conversations.xmpp.Jid;
|
||||||
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
|
import im.conversations.android.xmpp.model.stanza.Iq;
|
||||||
import okhttp3.Headers;
|
import okhttp3.Headers;
|
||||||
import okhttp3.HttpUrl;
|
import okhttp3.HttpUrl;
|
||||||
|
|
||||||
|
@ -67,9 +67,9 @@ public class SlotRequester {
|
||||||
|
|
||||||
private ListenableFuture<Slot> requestHttpUploadLegacy(Account account, Jid host, DownloadableFile file, String mime) {
|
private ListenableFuture<Slot> requestHttpUploadLegacy(Account account, Jid host, DownloadableFile file, String mime) {
|
||||||
final SettableFuture<Slot> future = SettableFuture.create();
|
final SettableFuture<Slot> future = SettableFuture.create();
|
||||||
final IqPacket request = service.getIqGenerator().requestHttpUploadLegacySlot(host, file, mime);
|
final Iq request = service.getIqGenerator().requestHttpUploadLegacySlot(host, file, mime);
|
||||||
service.sendIqPacket(account, request, (a, packet) -> {
|
service.sendIqPacket(account, request, (packet) -> {
|
||||||
if (packet.getType() == IqPacket.TYPE.RESULT) {
|
if (packet.getType() == Iq.Type.RESULT) {
|
||||||
final Element slotElement = packet.findChild("slot", Namespace.HTTP_UPLOAD_LEGACY);
|
final Element slotElement = packet.findChild("slot", Namespace.HTTP_UPLOAD_LEGACY);
|
||||||
if (slotElement != null) {
|
if (slotElement != null) {
|
||||||
try {
|
try {
|
||||||
|
@ -97,9 +97,9 @@ public class SlotRequester {
|
||||||
|
|
||||||
private ListenableFuture<Slot> requestHttpUpload(Account account, Jid host, DownloadableFile file, String fname, String mime) {
|
private ListenableFuture<Slot> requestHttpUpload(Account account, Jid host, DownloadableFile file, String fname, String mime) {
|
||||||
final SettableFuture<Slot> future = SettableFuture.create();
|
final SettableFuture<Slot> future = SettableFuture.create();
|
||||||
final IqPacket request = service.getIqGenerator().requestHttpUploadSlot(host, file, fname, mime);
|
final Iq request = service.getIqGenerator().requestHttpUploadSlot(host, file, fname, mime);
|
||||||
service.sendIqPacket(account, request, (a, packet) -> {
|
service.sendIqPacket(account, request, (packet) -> {
|
||||||
if (packet.getType() == IqPacket.TYPE.RESULT) {
|
if (packet.getType() == Iq.Type.RESULT) {
|
||||||
final Element slotElement = packet.findChild("slot", Namespace.HTTP_UPLOAD);
|
final Element slotElement = packet.findChild("slot", Namespace.HTTP_UPLOAD);
|
||||||
if (slotElement != null) {
|
if (slotElement != null) {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -18,14 +18,16 @@ import eu.siacs.conversations.services.XmppConnectionService;
|
||||||
import eu.siacs.conversations.xml.Element;
|
import eu.siacs.conversations.xml.Element;
|
||||||
import eu.siacs.conversations.xmpp.InvalidJid;
|
import eu.siacs.conversations.xmpp.InvalidJid;
|
||||||
import eu.siacs.conversations.xmpp.Jid;
|
import eu.siacs.conversations.xmpp.Jid;
|
||||||
import eu.siacs.conversations.xmpp.stanzas.AbstractStanza;
|
import im.conversations.android.xmpp.model.stanza.Stanza;
|
||||||
|
|
||||||
public abstract class AbstractParser {
|
public abstract class AbstractParser {
|
||||||
|
|
||||||
protected XmppConnectionService mXmppConnectionService;
|
protected final XmppConnectionService mXmppConnectionService;
|
||||||
|
protected final Account account;
|
||||||
|
|
||||||
protected AbstractParser(XmppConnectionService service) {
|
protected AbstractParser(final XmppConnectionService service, final Account account) {
|
||||||
this.mXmppConnectionService = service;
|
this.mXmppConnectionService = service;
|
||||||
|
this.account = account;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Long parseTimestamp(Element element, Long d) {
|
public static Long parseTimestamp(Element element, Long d) {
|
||||||
|
@ -36,8 +38,8 @@ public abstract class AbstractParser {
|
||||||
long min = Long.MAX_VALUE;
|
long min = Long.MAX_VALUE;
|
||||||
boolean returnDefault = true;
|
boolean returnDefault = true;
|
||||||
final Jid to;
|
final Jid to;
|
||||||
if (ignoreCsiAndSm && element instanceof AbstractStanza) {
|
if (ignoreCsiAndSm && element instanceof Stanza stanza) {
|
||||||
to = ((AbstractStanza) element).getTo();
|
to = stanza.getTo();
|
||||||
} else {
|
} else {
|
||||||
to = null;
|
to = null;
|
||||||
}
|
}
|
||||||
|
@ -125,7 +127,7 @@ public abstract class AbstractParser {
|
||||||
contact.setLastResource(from.isBareJid() ? "" : from.getResource());
|
contact.setLastResource(from.isBareJid() ? "" : from.getResource());
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String avatarData(Element items) {
|
protected static String avatarData(Element items) {
|
||||||
Element item = items.findChild("item");
|
Element item = items.findChild("item");
|
||||||
if (item == null) {
|
if (item == null) {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -26,6 +26,7 @@ import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import eu.siacs.conversations.Config;
|
import eu.siacs.conversations.Config;
|
||||||
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
|
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
|
||||||
|
@ -38,18 +39,17 @@ import eu.siacs.conversations.xml.Element;
|
||||||
import eu.siacs.conversations.xml.Namespace;
|
import eu.siacs.conversations.xml.Namespace;
|
||||||
import eu.siacs.conversations.xmpp.InvalidJid;
|
import eu.siacs.conversations.xmpp.InvalidJid;
|
||||||
import eu.siacs.conversations.xmpp.Jid;
|
import eu.siacs.conversations.xmpp.Jid;
|
||||||
import eu.siacs.conversations.xmpp.OnIqPacketReceived;
|
|
||||||
import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
|
import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
|
||||||
import eu.siacs.conversations.xmpp.forms.Data;
|
import eu.siacs.conversations.xmpp.forms.Data;
|
||||||
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
|
import im.conversations.android.xmpp.model.stanza.Iq;
|
||||||
|
|
||||||
public class IqParser extends AbstractParser implements OnIqPacketReceived {
|
public class IqParser extends AbstractParser implements Consumer<Iq> {
|
||||||
|
|
||||||
public IqParser(final XmppConnectionService service) {
|
public IqParser(final XmppConnectionService service, final Account account) {
|
||||||
super(service);
|
super(service, account);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<Jid> items(IqPacket packet) {
|
public static List<Jid> items(final Iq packet) {
|
||||||
ArrayList<Jid> items = new ArrayList<>();
|
ArrayList<Jid> items = new ArrayList<>();
|
||||||
final Element query = packet.findChild("query", Namespace.DISCO_ITEMS);
|
final Element query = packet.findChild("query", Namespace.DISCO_ITEMS);
|
||||||
if (query == null) {
|
if (query == null) {
|
||||||
|
@ -66,7 +66,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
|
||||||
return items;
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Room parseRoom(IqPacket packet) {
|
public static Room parseRoom(Iq packet) {
|
||||||
final Element query = packet.findChild("query", Namespace.DISCO_INFO);
|
final Element query = packet.findChild("query", Namespace.DISCO_INFO);
|
||||||
if (query == null) {
|
if (query == null) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -144,7 +144,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
|
||||||
mXmppConnectionService.syncRoster(account);
|
mXmppConnectionService.syncRoster(account);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String avatarData(final IqPacket packet) {
|
public static String avatarData(final Iq packet) {
|
||||||
final Element pubsub = packet.findChild("pubsub", Namespace.PUBSUB);
|
final Element pubsub = packet.findChild("pubsub", Namespace.PUBSUB);
|
||||||
if (pubsub == null) {
|
if (pubsub == null) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -153,10 +153,10 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
|
||||||
if (items == null) {
|
if (items == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return super.avatarData(items);
|
return AbstractParser.avatarData(items);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Element getItem(final IqPacket packet) {
|
public static Element getItem(final Iq packet) {
|
||||||
final Element pubsub = packet.findChild("pubsub", Namespace.PUBSUB);
|
final Element pubsub = packet.findChild("pubsub", Namespace.PUBSUB);
|
||||||
if (pubsub == null) {
|
if (pubsub == null) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -169,7 +169,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public Set<Integer> deviceIds(final Element item) {
|
public static Set<Integer> deviceIds(final Element item) {
|
||||||
Set<Integer> deviceIds = new HashSet<>();
|
Set<Integer> deviceIds = new HashSet<>();
|
||||||
if (item != null) {
|
if (item != null) {
|
||||||
final Element list = item.findChild("list");
|
final Element list = item.findChild("list");
|
||||||
|
@ -190,7 +190,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
|
||||||
return deviceIds;
|
return deviceIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Integer signedPreKeyId(final Element bundle) {
|
private static Integer signedPreKeyId(final Element bundle) {
|
||||||
final Element signedPreKeyPublic = bundle.findChild("signedPreKeyPublic");
|
final Element signedPreKeyPublic = bundle.findChild("signedPreKeyPublic");
|
||||||
if (signedPreKeyPublic == null) {
|
if (signedPreKeyPublic == null) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -202,7 +202,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ECPublicKey signedPreKeyPublic(final Element bundle) {
|
private static ECPublicKey signedPreKeyPublic(final Element bundle) {
|
||||||
ECPublicKey publicKey = null;
|
ECPublicKey publicKey = null;
|
||||||
final String signedPreKeyPublic = bundle.findChildContent("signedPreKeyPublic");
|
final String signedPreKeyPublic = bundle.findChildContent("signedPreKeyPublic");
|
||||||
if (signedPreKeyPublic == null) {
|
if (signedPreKeyPublic == null) {
|
||||||
|
@ -216,7 +216,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
|
||||||
return publicKey;
|
return publicKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] signedPreKeySignature(final Element bundle) {
|
private static byte[] signedPreKeySignature(final Element bundle) {
|
||||||
final String signedPreKeySignature = bundle.findChildContent("signedPreKeySignature");
|
final String signedPreKeySignature = bundle.findChildContent("signedPreKeySignature");
|
||||||
if (signedPreKeySignature == null) {
|
if (signedPreKeySignature == null) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -229,7 +229,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private IdentityKey identityKey(final Element bundle) {
|
private static IdentityKey identityKey(final Element bundle) {
|
||||||
final String identityKey = bundle.findChildContent("identityKey");
|
final String identityKey = bundle.findChildContent("identityKey");
|
||||||
if (identityKey == null) {
|
if (identityKey == null) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -242,7 +242,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<Integer, ECPublicKey> preKeyPublics(final IqPacket packet) {
|
public static Map<Integer, ECPublicKey> preKeyPublics(final Iq packet) {
|
||||||
Map<Integer, ECPublicKey> preKeyRecords = new HashMap<>();
|
Map<Integer, ECPublicKey> preKeyRecords = new HashMap<>();
|
||||||
Element item = getItem(packet);
|
Element item = getItem(packet);
|
||||||
if (item == null) {
|
if (item == null) {
|
||||||
|
@ -285,7 +285,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
|
||||||
return BaseEncoding.base64().decode(CharMatcher.whitespace().removeFrom(input));
|
return BaseEncoding.base64().decode(CharMatcher.whitespace().removeFrom(input));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Pair<X509Certificate[], byte[]> verification(final IqPacket packet) {
|
public static Pair<X509Certificate[], byte[]> verification(final Iq packet) {
|
||||||
Element item = getItem(packet);
|
Element item = getItem(packet);
|
||||||
Element verification = item != null ? item.findChild("verification", AxolotlService.PEP_PREFIX) : null;
|
Element verification = item != null ? item.findChild("verification", AxolotlService.PEP_PREFIX) : null;
|
||||||
Element chain = verification != null ? verification.findChild("chain") : null;
|
Element chain = verification != null ? verification.findChild("chain") : null;
|
||||||
|
@ -313,7 +313,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public PreKeyBundle bundle(final IqPacket bundle) {
|
public static PreKeyBundle bundle(final Iq bundle) {
|
||||||
final Element bundleItem = getItem(bundle);
|
final Element bundleItem = getItem(bundle);
|
||||||
if (bundleItem == null) {
|
if (bundleItem == null) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -337,7 +337,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
|
||||||
signedPreKeyId, signedPreKeyPublic, signedPreKeySignature, identityKey);
|
signedPreKeyId, signedPreKeyPublic, signedPreKeySignature, identityKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<PreKeyBundle> preKeys(final IqPacket preKeys) {
|
public static List<PreKeyBundle> preKeys(final Iq preKeys) {
|
||||||
List<PreKeyBundle> bundles = new ArrayList<>();
|
List<PreKeyBundle> bundles = new ArrayList<>();
|
||||||
Map<Integer, ECPublicKey> preKeyPublics = preKeyPublics(preKeys);
|
Map<Integer, ECPublicKey> preKeyPublics = preKeyPublics(preKeys);
|
||||||
if (preKeyPublics != null) {
|
if (preKeyPublics != null) {
|
||||||
|
@ -352,15 +352,15 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onIqPacketReceived(final Account account, final IqPacket packet) {
|
public void accept(final Iq packet) {
|
||||||
final boolean isGet = packet.getType() == IqPacket.TYPE.GET;
|
final boolean isGet = packet.getType() == Iq.Type.GET;
|
||||||
if (packet.getType() == IqPacket.TYPE.ERROR || packet.getType() == IqPacket.TYPE.TIMEOUT) {
|
if (packet.getType() == Iq.Type.ERROR || packet.getType() == Iq.Type.TIMEOUT) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (packet.hasChild("query", Namespace.ROSTER) && packet.fromServer(account)) {
|
if (packet.hasChild("query", Namespace.ROSTER) && packet.fromServer(account)) {
|
||||||
final Element query = packet.findChild("query");
|
final Element query = packet.findChild("query");
|
||||||
// If this is in response to a query for the whole roster:
|
// If this is in response to a query for the whole roster:
|
||||||
if (packet.getType() == IqPacket.TYPE.RESULT) {
|
if (packet.getType() == Iq.Type.RESULT) {
|
||||||
account.getRoster().markAllAsNotInRoster();
|
account.getRoster().markAllAsNotInRoster();
|
||||||
}
|
}
|
||||||
this.rosterItems(account, query);
|
this.rosterItems(account, query);
|
||||||
|
@ -374,7 +374,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
|
||||||
(block != null ? block.getChildren() : null);
|
(block != null ? block.getChildren() : null);
|
||||||
// If this is a response to a blocklist query, clear the block list and replace with the new one.
|
// If this is a response to a blocklist query, clear the block list and replace with the new one.
|
||||||
// Otherwise, just update the existing blocklist.
|
// Otherwise, just update the existing blocklist.
|
||||||
if (packet.getType() == IqPacket.TYPE.RESULT) {
|
if (packet.getType() == Iq.Type.RESULT) {
|
||||||
account.clearBlocklist();
|
account.clearBlocklist();
|
||||||
account.getXmppConnection().getFeatures().setBlockListRequested(true);
|
account.getXmppConnection().getFeatures().setBlockListRequested(true);
|
||||||
}
|
}
|
||||||
|
@ -390,7 +390,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
account.getBlocklist().addAll(jids);
|
account.getBlocklist().addAll(jids);
|
||||||
if (packet.getType() == IqPacket.TYPE.SET) {
|
if (packet.getType() == Iq.Type.SET) {
|
||||||
boolean removed = false;
|
boolean removed = false;
|
||||||
for (Jid jid : jids) {
|
for (Jid jid : jids) {
|
||||||
removed |= mXmppConnectionService.removeBlockedConversations(account, jid);
|
removed |= mXmppConnectionService.removeBlockedConversations(account, jid);
|
||||||
|
@ -402,15 +402,15 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
|
||||||
}
|
}
|
||||||
// Update the UI
|
// Update the UI
|
||||||
mXmppConnectionService.updateBlocklistUi(OnUpdateBlocklist.Status.BLOCKED);
|
mXmppConnectionService.updateBlocklistUi(OnUpdateBlocklist.Status.BLOCKED);
|
||||||
if (packet.getType() == IqPacket.TYPE.SET) {
|
if (packet.getType() == Iq.Type.SET) {
|
||||||
final IqPacket response = packet.generateResponse(IqPacket.TYPE.RESULT);
|
final Iq response = packet.generateResponse(Iq.Type.RESULT);
|
||||||
mXmppConnectionService.sendIqPacket(account, response, null);
|
mXmppConnectionService.sendIqPacket(account, response, null);
|
||||||
}
|
}
|
||||||
} else if (packet.hasChild("unblock", Namespace.BLOCKING) &&
|
} else if (packet.hasChild("unblock", Namespace.BLOCKING) &&
|
||||||
packet.fromServer(account) && packet.getType() == IqPacket.TYPE.SET) {
|
packet.fromServer(account) && packet.getType() == Iq.Type.SET) {
|
||||||
Log.d(Config.LOGTAG, "Received unblock update from server");
|
Log.d(Config.LOGTAG, "Received unblock update from server");
|
||||||
final Collection<Element> items = packet.findChild("unblock", Namespace.BLOCKING).getChildren();
|
final Collection<Element> items = packet.findChild("unblock", Namespace.BLOCKING).getChildren();
|
||||||
if (items.size() == 0) {
|
if (items.isEmpty()) {
|
||||||
// No children to unblock == unblock all
|
// No children to unblock == unblock all
|
||||||
account.getBlocklist().clear();
|
account.getBlocklist().clear();
|
||||||
} else {
|
} else {
|
||||||
|
@ -426,7 +426,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
|
||||||
account.getBlocklist().removeAll(jids);
|
account.getBlocklist().removeAll(jids);
|
||||||
}
|
}
|
||||||
mXmppConnectionService.updateBlocklistUi(OnUpdateBlocklist.Status.UNBLOCKED);
|
mXmppConnectionService.updateBlocklistUi(OnUpdateBlocklist.Status.UNBLOCKED);
|
||||||
final IqPacket response = packet.generateResponse(IqPacket.TYPE.RESULT);
|
final Iq response = packet.generateResponse(Iq.Type.RESULT);
|
||||||
mXmppConnectionService.sendIqPacket(account, response, null);
|
mXmppConnectionService.sendIqPacket(account, response, null);
|
||||||
} else if (packet.hasChild("open", "http://jabber.org/protocol/ibb")
|
} else if (packet.hasChild("open", "http://jabber.org/protocol/ibb")
|
||||||
|| packet.hasChild("data", "http://jabber.org/protocol/ibb")
|
|| packet.hasChild("data", "http://jabber.org/protocol/ibb")
|
||||||
|
@ -434,18 +434,18 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
|
||||||
mXmppConnectionService.getJingleConnectionManager()
|
mXmppConnectionService.getJingleConnectionManager()
|
||||||
.deliverIbbPacket(account, packet);
|
.deliverIbbPacket(account, packet);
|
||||||
} else if (packet.hasChild("query", "http://jabber.org/protocol/disco#info")) {
|
} else if (packet.hasChild("query", "http://jabber.org/protocol/disco#info")) {
|
||||||
final IqPacket response = mXmppConnectionService.getIqGenerator().discoResponse(account, packet);
|
final Iq response = mXmppConnectionService.getIqGenerator().discoResponse(account, packet);
|
||||||
mXmppConnectionService.sendIqPacket(account, response, null);
|
mXmppConnectionService.sendIqPacket(account, response, null);
|
||||||
} else if (packet.hasChild("query", "jabber:iq:version") && isGet) {
|
} else if (packet.hasChild("query", "jabber:iq:version") && isGet) {
|
||||||
final IqPacket response = mXmppConnectionService.getIqGenerator().versionResponse(packet);
|
final Iq response = mXmppConnectionService.getIqGenerator().versionResponse(packet);
|
||||||
mXmppConnectionService.sendIqPacket(account, response, null);
|
mXmppConnectionService.sendIqPacket(account, response, null);
|
||||||
} else if (packet.hasChild("ping", "urn:xmpp:ping") && isGet) {
|
} else if (packet.hasChild("ping", "urn:xmpp:ping") && isGet) {
|
||||||
final IqPacket response = packet.generateResponse(IqPacket.TYPE.RESULT);
|
final Iq response = packet.generateResponse(Iq.Type.RESULT);
|
||||||
mXmppConnectionService.sendIqPacket(account, response, null);
|
mXmppConnectionService.sendIqPacket(account, response, null);
|
||||||
} else if (packet.hasChild("time", "urn:xmpp:time") && isGet) {
|
} else if (packet.hasChild("time", "urn:xmpp:time") && isGet) {
|
||||||
final IqPacket response;
|
final Iq response;
|
||||||
if (mXmppConnectionService.useTorToConnect() || account.isOnion()) {
|
if (mXmppConnectionService.useTorToConnect() || account.isOnion()) {
|
||||||
response = packet.generateResponse(IqPacket.TYPE.ERROR);
|
response = packet.generateResponse(Iq.Type.ERROR);
|
||||||
final Element error = response.addChild("error");
|
final Element error = response.addChild("error");
|
||||||
error.setAttribute("type", "cancel");
|
error.setAttribute("type", "cancel");
|
||||||
error.addChild("not-allowed", "urn:ietf:params:xml:ns:xmpp-stanzas");
|
error.addChild("not-allowed", "urn:ietf:params:xml:ns:xmpp-stanzas");
|
||||||
|
@ -453,18 +453,18 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
|
||||||
response = mXmppConnectionService.getIqGenerator().entityTimeResponse(packet);
|
response = mXmppConnectionService.getIqGenerator().entityTimeResponse(packet);
|
||||||
}
|
}
|
||||||
mXmppConnectionService.sendIqPacket(account, response, null);
|
mXmppConnectionService.sendIqPacket(account, response, null);
|
||||||
} else if (packet.hasChild("push", Namespace.UNIFIED_PUSH) && packet.getType() == IqPacket.TYPE.SET) {
|
} else if (packet.hasChild("push", Namespace.UNIFIED_PUSH) && packet.getType() == Iq.Type.SET) {
|
||||||
final Jid transport = packet.getFrom();
|
final Jid transport = packet.getFrom();
|
||||||
final Element push = packet.findChild("push", Namespace.UNIFIED_PUSH);
|
final Element push = packet.findChild("push", Namespace.UNIFIED_PUSH);
|
||||||
final boolean success =
|
final boolean success =
|
||||||
push != null
|
push != null
|
||||||
&& mXmppConnectionService.processUnifiedPushMessage(
|
&& mXmppConnectionService.processUnifiedPushMessage(
|
||||||
account, transport, push);
|
account, transport, push);
|
||||||
final IqPacket response;
|
final Iq response;
|
||||||
if (success) {
|
if (success) {
|
||||||
response = packet.generateResponse(IqPacket.TYPE.RESULT);
|
response = packet.generateResponse(Iq.Type.RESULT);
|
||||||
} else {
|
} else {
|
||||||
response = packet.generateResponse(IqPacket.TYPE.ERROR);
|
response = packet.generateResponse(Iq.Type.ERROR);
|
||||||
final Element error = response.addChild("error");
|
final Element error = response.addChild("error");
|
||||||
error.setAttribute("type", "cancel");
|
error.setAttribute("type", "cancel");
|
||||||
error.setAttribute("code", "404");
|
error.setAttribute("code", "404");
|
||||||
|
@ -476,8 +476,8 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
|
||||||
final Conversation conversation = mXmppConnectionService.find(account, packet.getFrom());
|
final Conversation conversation = mXmppConnectionService.find(account, packet.getFrom());
|
||||||
if (packet.hasChild("data", "urn:xmpp:bob") && isGet && (conversation == null ? contact != null && contact.canInferPresence() : conversation.canInferPresence())) {
|
if (packet.hasChild("data", "urn:xmpp:bob") && isGet && (conversation == null ? contact != null && contact.canInferPresence() : conversation.canInferPresence())) {
|
||||||
mXmppConnectionService.sendIqPacket(account, mXmppConnectionService.getIqGenerator().bobResponse(packet), null);
|
mXmppConnectionService.sendIqPacket(account, mXmppConnectionService.getIqGenerator().bobResponse(packet), null);
|
||||||
} else if (packet.getType() == IqPacket.TYPE.GET || packet.getType() == IqPacket.TYPE.SET) {
|
} else if (packet.getType() == Iq.Type.GET || packet.getType() == Iq.Type.SET) {
|
||||||
final IqPacket response = packet.generateResponse(IqPacket.TYPE.ERROR);
|
final var response = packet.generateResponse(Iq.Type.ERROR);
|
||||||
final Element error = response.addChild("error");
|
final Element error = response.addChild("error");
|
||||||
error.setAttribute("type", "cancel");
|
error.setAttribute("type", "cancel");
|
||||||
error.addChild("feature-not-implemented", "urn:ietf:params:xml:ns:xmpp-stanzas");
|
error.addChild("feature-not-implemented", "urn:ietf:params:xml:ns:xmpp-stanzas");
|
||||||
|
|
|
@ -22,6 +22,7 @@ import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import io.ipfs.cid.Cid;
|
import io.ipfs.cid.Cid;
|
||||||
|
|
||||||
|
@ -60,17 +61,20 @@ import eu.siacs.conversations.xmpp.forms.Data;
|
||||||
import eu.siacs.conversations.xmpp.jingle.JingleConnectionManager;
|
import eu.siacs.conversations.xmpp.jingle.JingleConnectionManager;
|
||||||
import eu.siacs.conversations.xmpp.jingle.JingleRtpConnection;
|
import eu.siacs.conversations.xmpp.jingle.JingleRtpConnection;
|
||||||
import eu.siacs.conversations.xmpp.pep.Avatar;
|
import eu.siacs.conversations.xmpp.pep.Avatar;
|
||||||
import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
|
import im.conversations.android.xmpp.model.Extension;
|
||||||
|
import im.conversations.android.xmpp.model.carbons.Received;
|
||||||
|
import im.conversations.android.xmpp.model.carbons.Sent;
|
||||||
|
import im.conversations.android.xmpp.model.forward.Forwarded;
|
||||||
|
|
||||||
public class MessageParser extends AbstractParser implements OnMessagePacketReceived {
|
public class MessageParser extends AbstractParser implements Consumer<im.conversations.android.xmpp.model.stanza.Message> {
|
||||||
|
|
||||||
private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm:ss", Locale.ENGLISH);
|
private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm:ss", Locale.ENGLISH);
|
||||||
|
|
||||||
private static final List<String> JINGLE_MESSAGE_ELEMENT_NAMES =
|
private static final List<String> JINGLE_MESSAGE_ELEMENT_NAMES =
|
||||||
Arrays.asList("accept", "propose", "proceed", "reject", "retract", "ringing", "finish");
|
Arrays.asList("accept", "propose", "proceed", "reject", "retract", "ringing", "finish");
|
||||||
|
|
||||||
public MessageParser(XmppConnectionService service) {
|
public MessageParser(final XmppConnectionService service, final Account account) {
|
||||||
super(service);
|
super(service, account);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String extractStanzaId(Element packet, boolean isTypeGroupChat, Conversation conversation) {
|
private static String extractStanzaId(Element packet, boolean isTypeGroupChat, Conversation conversation) {
|
||||||
|
@ -109,7 +113,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
|
||||||
return result != null ? result : fallback;
|
return result != null ? result : fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean extractChatState(Conversation c, final boolean isTypeGroupChat, final MessagePacket packet) {
|
private boolean extractChatState(Conversation c, final boolean isTypeGroupChat, final im.conversations.android.xmpp.model.stanza.Message packet) {
|
||||||
ChatState state = ChatState.parse(packet);
|
ChatState state = ChatState.parse(packet);
|
||||||
if (state != null && c != null) {
|
if (state != null && c != null) {
|
||||||
final Account account = c.getAccount();
|
final Account account = c.getAccount();
|
||||||
|
@ -251,7 +255,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
|
||||||
}
|
}
|
||||||
} else if (AxolotlService.PEP_DEVICE_LIST.equals(node)) {
|
} else if (AxolotlService.PEP_DEVICE_LIST.equals(node)) {
|
||||||
Element item = items.findChild("item");
|
Element item = items.findChild("item");
|
||||||
Set<Integer> deviceIds = mXmppConnectionService.getIqParser().deviceIds(item);
|
final Set<Integer> deviceIds = IqParser.deviceIds(item);
|
||||||
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received PEP device list " + deviceIds + " update from " + from + ", processing... ");
|
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received PEP device list " + deviceIds + " update from " + from + ", processing... ");
|
||||||
final AxolotlService axolotlService = account.getAxolotlService();
|
final AxolotlService axolotlService = account.getAxolotlService();
|
||||||
axolotlService.registerDevices(from, deviceIds);
|
axolotlService.registerDevices(from, deviceIds);
|
||||||
|
@ -358,10 +362,10 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
|
||||||
mXmppConnectionService.updateAccountUi();
|
mXmppConnectionService.updateAccountUi();
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean handleErrorMessage(final Account account, final MessagePacket packet) {
|
private boolean handleErrorMessage(final Account account, final im.conversations.android.xmpp.model.stanza.Message packet) {
|
||||||
if (packet.getType() == MessagePacket.TYPE_ERROR) {
|
if (packet.getType() == im.conversations.android.xmpp.model.stanza.Message.Type.ERROR) {
|
||||||
if (packet.fromServer(account)) {
|
if (packet.fromServer(account)) {
|
||||||
final Pair<MessagePacket, Long> forwarded = packet.getForwardedMessagePacket("received", Namespace.CARBONS);
|
final var forwarded = getForwardedMessagePacket(packet,"received", Namespace.CARBONS);
|
||||||
if (forwarded != null) {
|
if (forwarded != null) {
|
||||||
return handleErrorMessage(account, forwarded.first);
|
return handleErrorMessage(account, forwarded.first);
|
||||||
}
|
}
|
||||||
|
@ -404,11 +408,11 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onMessagePacketReceived(Account account, MessagePacket original) {
|
public void accept(final im.conversations.android.xmpp.model.stanza.Message original) {
|
||||||
if (handleErrorMessage(account, original)) {
|
if (handleErrorMessage(account, original)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final MessagePacket packet;
|
final im.conversations.android.xmpp.model.stanza.Message packet;
|
||||||
Long timestamp = null;
|
Long timestamp = null;
|
||||||
boolean isCarbon = false;
|
boolean isCarbon = false;
|
||||||
String serverMsgId = null;
|
String serverMsgId = null;
|
||||||
|
@ -422,7 +426,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
|
||||||
final MessageArchiveService.Query query = queryId == null ? null : mXmppConnectionService.getMessageArchiveService().findQuery(queryId);
|
final MessageArchiveService.Query query = queryId == null ? null : mXmppConnectionService.getMessageArchiveService().findQuery(queryId);
|
||||||
final boolean offlineMessagesRetrieved = account.getXmppConnection().isOfflineMessagesRetrieved();
|
final boolean offlineMessagesRetrieved = account.getXmppConnection().isOfflineMessagesRetrieved();
|
||||||
if (query != null && query.validFrom(original.getFrom())) {
|
if (query != null && query.validFrom(original.getFrom())) {
|
||||||
final Pair<MessagePacket, Long> f = original.getForwardedMessagePacket("result", query.version.namespace);
|
final var f = getForwardedMessagePacket(original,"result", query.version.namespace);
|
||||||
if (f == null) {
|
if (f == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -442,9 +446,9 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
|
||||||
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received mam result with invalid from (" + original.getFrom() + ") or queryId (" + queryId + ")");
|
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received mam result with invalid from (" + original.getFrom() + ") or queryId (" + queryId + ")");
|
||||||
return;
|
return;
|
||||||
} else if (original.fromServer(account)) {
|
} else if (original.fromServer(account)) {
|
||||||
Pair<MessagePacket, Long> f;
|
Pair<im.conversations.android.xmpp.model.stanza.Message, Long> f;
|
||||||
f = original.getForwardedMessagePacket("received", Namespace.CARBONS);
|
f = getForwardedMessagePacket(original, Received.class);
|
||||||
f = f == null ? original.getForwardedMessagePacket("sent", Namespace.CARBONS) : f;
|
f = f == null ? getForwardedMessagePacket(original, Sent.class) : f;
|
||||||
packet = f != null ? f.first : original;
|
packet = f != null ? f.first : original;
|
||||||
if (handleErrorMessage(account, packet)) {
|
if (handleErrorMessage(account, packet)) {
|
||||||
return;
|
return;
|
||||||
|
@ -514,7 +518,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isTypeGroupChat = packet.getType() == MessagePacket.TYPE_GROUPCHAT;
|
boolean isTypeGroupChat = packet.getType() == im.conversations.android.xmpp.model.stanza.Message.Type.GROUPCHAT;
|
||||||
if (query != null && !query.muc() && isTypeGroupChat) {
|
if (query != null && !query.muc() && isTypeGroupChat) {
|
||||||
Log.e(Config.LOGTAG, account.getJid().asBareJid() + ": received groupchat (" + from + ") message on regular MAM request. skipping");
|
Log.e(Config.LOGTAG, account.getJid().asBareJid() + ": received groupchat (" + from + ") message on regular MAM request. skipping");
|
||||||
return;
|
return;
|
||||||
|
@ -1300,6 +1304,34 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static Pair<im.conversations.android.xmpp.model.stanza.Message,Long> getForwardedMessagePacket(final im.conversations.android.xmpp.model.stanza.Message original, Class<? extends Extension> clazz) {
|
||||||
|
final var extension = original.getExtension(clazz);
|
||||||
|
final var forwarded = extension == null ? null : extension.getExtension(Forwarded.class);
|
||||||
|
if (forwarded == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final Long timestamp = AbstractParser.parseTimestamp(forwarded, null);
|
||||||
|
final var forwardedMessage = forwarded.getMessage();
|
||||||
|
if (forwardedMessage == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new Pair<>(forwardedMessage,timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Pair<im.conversations.android.xmpp.model.stanza.Message,Long> getForwardedMessagePacket(final im.conversations.android.xmpp.model.stanza.Message original, final String name, final String namespace) {
|
||||||
|
final Element wrapper = original.findChild(name, namespace);
|
||||||
|
final var forwardedElement = wrapper == null ? null : wrapper.findChild("forwarded",Namespace.FORWARD);
|
||||||
|
if (forwardedElement instanceof Forwarded forwarded) {
|
||||||
|
final Long timestamp = AbstractParser.parseTimestamp(forwarded, null);
|
||||||
|
final var forwardedMessage = forwarded.getMessage();
|
||||||
|
if (forwardedMessage == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return new Pair<>(forwardedMessage,timestamp);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
private void dismissNotification(Account account, Jid counterpart, MessageArchiveService.Query query, final String id) {
|
private void dismissNotification(Account account, Jid counterpart, MessageArchiveService.Query query, final String id) {
|
||||||
final Conversation conversation = mXmppConnectionService.find(account, counterpart.asBareJid());
|
final Conversation conversation = mXmppConnectionService.find(account, counterpart.asBareJid());
|
||||||
if (conversation != null && (query == null || query.isCatchup())) {
|
if (conversation != null && (query == null || query.isCatchup())) {
|
||||||
|
@ -1312,7 +1344,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processMessageReceipts(final Account account, final MessagePacket packet, final String remoteMsgId, MessageArchiveService.Query query) {
|
private void processMessageReceipts(final Account account, final im.conversations.android.xmpp.model.stanza.Message packet, final String remoteMsgId, MessageArchiveService.Query query) {
|
||||||
final boolean markable = packet.hasChild("markable", "urn:xmpp:chat-markers:0");
|
final boolean markable = packet.hasChild("markable", "urn:xmpp:chat-markers:0");
|
||||||
final boolean request = packet.hasChild("request", "urn:xmpp:receipts");
|
final boolean request = packet.hasChild("request", "urn:xmpp:receipts");
|
||||||
if (query == null) {
|
if (query == null) {
|
||||||
|
@ -1324,7 +1356,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
|
||||||
receiptsNamespaces.add("urn:xmpp:receipts");
|
receiptsNamespaces.add("urn:xmpp:receipts");
|
||||||
}
|
}
|
||||||
if (receiptsNamespaces.size() > 0) {
|
if (receiptsNamespaces.size() > 0) {
|
||||||
final MessagePacket receipt = mXmppConnectionService.getMessageGenerator().received(account,
|
final var receipt = mXmppConnectionService.getMessageGenerator().received(account,
|
||||||
packet.getFrom(),
|
packet.getFrom(),
|
||||||
remoteMsgId,
|
remoteMsgId,
|
||||||
receiptsNamespaces,
|
receiptsNamespaces,
|
||||||
|
|
|
@ -19,22 +19,21 @@ import eu.siacs.conversations.xml.Element;
|
||||||
import eu.siacs.conversations.xml.Namespace;
|
import eu.siacs.conversations.xml.Namespace;
|
||||||
import eu.siacs.conversations.xmpp.InvalidJid;
|
import eu.siacs.conversations.xmpp.InvalidJid;
|
||||||
import eu.siacs.conversations.xmpp.Jid;
|
import eu.siacs.conversations.xmpp.Jid;
|
||||||
import eu.siacs.conversations.xmpp.OnPresencePacketReceived;
|
|
||||||
import eu.siacs.conversations.xmpp.pep.Avatar;
|
import eu.siacs.conversations.xmpp.pep.Avatar;
|
||||||
import eu.siacs.conversations.xmpp.stanzas.PresencePacket;
|
|
||||||
|
|
||||||
import org.openintents.openpgp.util.OpenPgpUtils;
|
import org.openintents.openpgp.util.OpenPgpUtils;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
public class PresenceParser extends AbstractParser implements OnPresencePacketReceived {
|
public class PresenceParser extends AbstractParser implements Consumer<im.conversations.android.xmpp.model.stanza.Presence> {
|
||||||
|
|
||||||
public PresenceParser(XmppConnectionService service) {
|
public PresenceParser(final XmppConnectionService service, final Account account) {
|
||||||
super(service);
|
super(service, account);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void parseConferencePresence(PresencePacket packet, Account account) {
|
public void parseConferencePresence(final im.conversations.android.xmpp.model.stanza.Presence packet, Account account) {
|
||||||
final Conversation conversation =
|
final Conversation conversation =
|
||||||
packet.getFrom() == null
|
packet.getFrom() == null
|
||||||
? null
|
? null
|
||||||
|
@ -58,9 +57,7 @@ public class PresenceParser extends AbstractParser implements OnPresencePacketRe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processConferencePresence(PresencePacket packet, Conversation conversation) {
|
private void processConferencePresence(final im.conversations.android.xmpp.model.stanza.Presence packet, Conversation conversation) {
|
||||||
|
|
||||||
|
|
||||||
final Account account = conversation.getAccount();
|
final Account account = conversation.getAccount();
|
||||||
final MucOptions mucOptions = conversation.getMucOptions();
|
final MucOptions mucOptions = conversation.getMucOptions();
|
||||||
final Jid jid = conversation.getAccount().getJid();
|
final Jid jid = conversation.getAccount().getJid();
|
||||||
|
@ -300,7 +297,7 @@ public class PresenceParser extends AbstractParser implements OnPresencePacketRe
|
||||||
return codes;
|
return codes;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void parseContactPresence(final PresencePacket packet, final Account account) {
|
private void parseContactPresence(final im.conversations.android.xmpp.model.stanza.Presence packet, final Account account) {
|
||||||
final PresenceGenerator mPresenceGenerator = mXmppConnectionService.getPresenceGenerator();
|
final PresenceGenerator mPresenceGenerator = mXmppConnectionService.getPresenceGenerator();
|
||||||
final Jid from = packet.getFrom();
|
final Jid from = packet.getFrom();
|
||||||
if (from == null || from.equals(account.getJid())) {
|
if (from == null || from.equals(account.getJid())) {
|
||||||
|
@ -434,7 +431,7 @@ public class PresenceParser extends AbstractParser implements OnPresencePacketRe
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPresencePacketReceived(Account account, PresencePacket packet) {
|
public void accept(final im.conversations.android.xmpp.model.stanza.Presence packet) {
|
||||||
if (packet.hasChild("x", Namespace.MUC_USER)) {
|
if (packet.hasChild("x", Namespace.MUC_USER)) {
|
||||||
this.parseConferencePresence(packet, account);
|
this.parseConferencePresence(packet, account);
|
||||||
} else if (packet.hasChild("x", "http://jabber.org/protocol/muc")) {
|
} else if (packet.hasChild("x", "http://jabber.org/protocol/muc")) {
|
||||||
|
|
|
@ -53,6 +53,21 @@ import com.madebyevan.thumbhash.ThumbHash;
|
||||||
|
|
||||||
import com.wolt.blurhashkt.BlurHashDecoder;
|
import com.wolt.blurhashkt.BlurHashDecoder;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.Config;
|
||||||
|
import eu.siacs.conversations.R;
|
||||||
|
import eu.siacs.conversations.entities.Conversation;
|
||||||
|
import eu.siacs.conversations.entities.DownloadableFile;
|
||||||
|
import eu.siacs.conversations.entities.Message;
|
||||||
|
import eu.siacs.conversations.services.AttachFileToConversationRunnable;
|
||||||
|
import eu.siacs.conversations.services.XmppConnectionService;
|
||||||
|
import eu.siacs.conversations.ui.adapter.MediaAdapter;
|
||||||
|
import eu.siacs.conversations.ui.util.Attachment;
|
||||||
|
import eu.siacs.conversations.utils.CryptoHelper;
|
||||||
|
import eu.siacs.conversations.utils.FileUtils;
|
||||||
|
import eu.siacs.conversations.utils.FileWriterException;
|
||||||
|
import eu.siacs.conversations.utils.MimeUtils;
|
||||||
|
import eu.siacs.conversations.xmpp.pep.Avatar;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
|
@ -85,22 +100,6 @@ import org.tomlj.TomlTable;
|
||||||
|
|
||||||
import io.ipfs.cid.Cid;
|
import io.ipfs.cid.Cid;
|
||||||
|
|
||||||
import eu.siacs.conversations.Config;
|
|
||||||
import eu.siacs.conversations.R;
|
|
||||||
import eu.siacs.conversations.entities.Conversation;
|
|
||||||
import eu.siacs.conversations.entities.DownloadableFile;
|
|
||||||
import eu.siacs.conversations.entities.Message;
|
|
||||||
import eu.siacs.conversations.services.AttachFileToConversationRunnable;
|
|
||||||
import eu.siacs.conversations.services.XmppConnectionService;
|
|
||||||
import eu.siacs.conversations.ui.adapter.MediaAdapter;
|
|
||||||
import eu.siacs.conversations.ui.util.Attachment;
|
|
||||||
import eu.siacs.conversations.utils.CryptoHelper;
|
|
||||||
import eu.siacs.conversations.utils.FileUtils;
|
|
||||||
import eu.siacs.conversations.utils.FileWriterException;
|
|
||||||
import eu.siacs.conversations.utils.MimeUtils;
|
|
||||||
import eu.siacs.conversations.xmpp.pep.Avatar;
|
|
||||||
import eu.siacs.conversations.xml.Element;
|
|
||||||
|
|
||||||
public class FileBackend {
|
public class FileBackend {
|
||||||
|
|
||||||
private static final Object THUMBNAIL_LOCK = new Object();
|
private static final Object THUMBNAIL_LOCK = new Object();
|
||||||
|
@ -784,16 +783,16 @@ public class FileBackend {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void copyFileToPrivateStorage(File file, Uri uri) throws FileCopyException {
|
private void copyFileToPrivateStorage(File file, Uri uri) throws FileCopyException {
|
||||||
Log.d(
|
final var parentDirectory = file.getParentFile();
|
||||||
Config.LOGTAG,
|
if (parentDirectory != null && parentDirectory.mkdirs()) {
|
||||||
"copy file (" + uri.toString() + ") to private storage " + file.getAbsolutePath());
|
Log.d(Config.LOGTAG,"created directory "+parentDirectory.getAbsolutePath());
|
||||||
file.getParentFile().mkdirs();
|
}
|
||||||
try {
|
try {
|
||||||
if (!file.createNewFile() && file.length() > 0) {
|
if (!file.createNewFile() && file.length() > 0) {
|
||||||
if (file.canRead() && file.getName().startsWith("zb2")) return; // We have this content already
|
if (file.canRead() && file.getName().startsWith("zb2")) return; // We have this content already
|
||||||
throw new FileCopyException(R.string.error_unable_to_create_temporary_file);
|
throw new FileCopyException(R.string.error_unable_to_create_temporary_file);
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (final IOException e) {
|
||||||
throw new FileCopyException(R.string.error_unable_to_create_temporary_file);
|
throw new FileCopyException(R.string.error_unable_to_create_temporary_file);
|
||||||
}
|
}
|
||||||
try (final OutputStream os = new FileOutputStream(file);
|
try (final OutputStream os = new FileOutputStream(file);
|
||||||
|
@ -803,12 +802,12 @@ public class FileBackend {
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
ByteStreams.copy(is, os);
|
ByteStreams.copy(is, os);
|
||||||
} catch (IOException e) {
|
} catch (final IOException e) {
|
||||||
throw new FileWriterException(file);
|
throw new FileWriterException(file);
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
os.flush();
|
os.flush();
|
||||||
} catch (IOException e) {
|
} catch (final IOException e) {
|
||||||
throw new FileWriterException(file);
|
throw new FileWriterException(file);
|
||||||
}
|
}
|
||||||
} catch (final FileNotFoundException e) {
|
} catch (final FileNotFoundException e) {
|
||||||
|
@ -817,7 +816,7 @@ public class FileBackend {
|
||||||
} catch (final FileWriterException e) {
|
} catch (final FileWriterException e) {
|
||||||
cleanup(file);
|
cleanup(file);
|
||||||
throw new FileCopyException(R.string.error_unable_to_create_temporary_file);
|
throw new FileCopyException(R.string.error_unable_to_create_temporary_file);
|
||||||
} catch (final SecurityException | IllegalStateException e) {
|
} catch (final SecurityException | IllegalStateException | IllegalArgumentException e) {
|
||||||
cleanup(file);
|
cleanup(file);
|
||||||
throw new FileCopyException(R.string.error_security_exception);
|
throw new FileCopyException(R.string.error_security_exception);
|
||||||
} catch (final IOException e) {
|
} catch (final IOException e) {
|
||||||
|
@ -828,7 +827,7 @@ public class FileBackend {
|
||||||
|
|
||||||
public void copyFileToPrivateStorage(Message message, Uri uri, String type)
|
public void copyFileToPrivateStorage(Message message, Uri uri, String type)
|
||||||
throws FileCopyException {
|
throws FileCopyException {
|
||||||
String mime = MimeUtils.guessMimeTypeFromUriAndMime(mXmppConnectionService, uri, type);
|
final String mime = MimeUtils.guessMimeTypeFromUriAndMime(mXmppConnectionService, uri, type);
|
||||||
Log.d(Config.LOGTAG, "copy " + uri.toString() + " to private storage (mime=" + mime + ")");
|
Log.d(Config.LOGTAG, "copy " + uri.toString() + " to private storage (mime=" + mime + ")");
|
||||||
String extension = MimeUtils.guessExtensionFromMimeType(mime);
|
String extension = MimeUtils.guessExtensionFromMimeType(mime);
|
||||||
if (extension == null) {
|
if (extension == null) {
|
||||||
|
@ -1168,9 +1167,9 @@ public class FileBackend {
|
||||||
}
|
}
|
||||||
|
|
||||||
public BitmapDrawable getFallbackThumbnail(final Message message, int size, boolean cacheOnly) {
|
public BitmapDrawable getFallbackThumbnail(final Message message, int size, boolean cacheOnly) {
|
||||||
List<Element> thumbs = message.getFileParams() != null ? message.getFileParams().getThumbnails() : null;
|
final var thumbs = message.getFileParams() != null ? message.getFileParams().getThumbnails() : null;
|
||||||
if (thumbs != null && !thumbs.isEmpty()) {
|
if (thumbs != null && !thumbs.isEmpty()) {
|
||||||
for (Element thumb : thumbs) {
|
for (final var thumb : thumbs) {
|
||||||
final var uriS = thumb.getAttribute("uri");
|
final var uriS = thumb.getAttribute("uri");
|
||||||
if (uriS == null) continue;
|
if (uriS == null) continue;
|
||||||
Uri uri = Uri.parse(uriS);
|
Uri uri = Uri.parse(uriS);
|
||||||
|
@ -1240,9 +1239,9 @@ public class FileBackend {
|
||||||
|
|
||||||
if ((thumbnail == null) && (!cacheOnly)) {
|
if ((thumbnail == null) && (!cacheOnly)) {
|
||||||
synchronized (THUMBNAIL_LOCK) {
|
synchronized (THUMBNAIL_LOCK) {
|
||||||
List<Element> thumbs = message.getFileParams() != null ? message.getFileParams().getThumbnails() : null;
|
final var thumbs = message.getFileParams() != null ? message.getFileParams().getThumbnails() : null;
|
||||||
if (thumbs != null && !thumbs.isEmpty()) {
|
if (thumbs != null && !thumbs.isEmpty()) {
|
||||||
for (Element thumb : thumbs) {
|
for (final var thumb : thumbs) {
|
||||||
final var uriS = thumb.getAttribute("uri");
|
final var uriS = thumb.getAttribute("uri");
|
||||||
if (uriS == null) continue;
|
if (uriS == null) continue;
|
||||||
Uri uri = Uri.parse(uriS);
|
Uri uri = Uri.parse(uriS);
|
||||||
|
|
|
@ -42,8 +42,12 @@ public class CallIntegration extends Connection {
|
||||||
*
|
*
|
||||||
* <p>Samsung Galaxy Tab A claims to have FEATURE_CONNECTION_SERVICE but then throws
|
* <p>Samsung Galaxy Tab A claims to have FEATURE_CONNECTION_SERVICE but then throws
|
||||||
* SecurityException when invoking placeCall(). Both Stock and LineageOS have this problem.
|
* SecurityException when invoking placeCall(). Both Stock and LineageOS have this problem.
|
||||||
|
*
|
||||||
|
* <p>Lenovo Yoga Smart Tab YT-X705F claims to have FEATURE_CONNECTION_SERVICE but throws
|
||||||
|
* SecurityException
|
||||||
*/
|
*/
|
||||||
private static final List<String> BROKEN_DEVICE_MODELS = Arrays.asList("OnePlus6", "gtaxlwifi");
|
private static final List<String> BROKEN_DEVICE_MODELS =
|
||||||
|
Arrays.asList("OnePlus6", "gtaxlwifi", "YT-X705F");
|
||||||
|
|
||||||
public static final int DEFAULT_TONE_VOLUME = 60;
|
public static final int DEFAULT_TONE_VOLUME = 60;
|
||||||
private static final int DEFAULT_MEDIA_PLAYER_VOLUME = 90;
|
private static final int DEFAULT_MEDIA_PLAYER_VOLUME = 90;
|
||||||
|
@ -393,9 +397,7 @@ public class CallIntegration extends Connection {
|
||||||
|
|
||||||
public void success() {
|
public void success() {
|
||||||
Log.d(Config.LOGTAG, "CallIntegration.success()");
|
Log.d(Config.LOGTAG, "CallIntegration.success()");
|
||||||
final var toneGenerator =
|
startTone(DEFAULT_TONE_VOLUME, ToneGenerator.TONE_CDMA_CALLDROP_LITE, 375);
|
||||||
new ToneGenerator(AudioManager.STREAM_VOICE_CALL, DEFAULT_TONE_VOLUME);
|
|
||||||
toneGenerator.startTone(ToneGenerator.TONE_CDMA_CALLDROP_LITE, 375);
|
|
||||||
this.destroyWithDelay(new DisconnectCause(DisconnectCause.LOCAL, null), 375);
|
this.destroyWithDelay(new DisconnectCause(DisconnectCause.LOCAL, null), 375);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -410,9 +412,7 @@ public class CallIntegration extends Connection {
|
||||||
|
|
||||||
public void error() {
|
public void error() {
|
||||||
Log.d(Config.LOGTAG, "CallIntegration.error()");
|
Log.d(Config.LOGTAG, "CallIntegration.error()");
|
||||||
final var toneGenerator =
|
startTone(DEFAULT_TONE_VOLUME, ToneGenerator.TONE_CDMA_CALLDROP_LITE, 375);
|
||||||
new ToneGenerator(AudioManager.STREAM_VOICE_CALL, DEFAULT_TONE_VOLUME);
|
|
||||||
toneGenerator.startTone(ToneGenerator.TONE_CDMA_CALLDROP_LITE, 375);
|
|
||||||
this.destroyWithDelay(new DisconnectCause(DisconnectCause.ERROR, null), 375);
|
this.destroyWithDelay(new DisconnectCause(DisconnectCause.ERROR, null), 375);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -429,8 +429,7 @@ public class CallIntegration extends Connection {
|
||||||
|
|
||||||
public void busy() {
|
public void busy() {
|
||||||
Log.d(Config.LOGTAG, "CallIntegration.busy()");
|
Log.d(Config.LOGTAG, "CallIntegration.busy()");
|
||||||
final var toneGenerator = new ToneGenerator(AudioManager.STREAM_VOICE_CALL, 80);
|
startTone(80, ToneGenerator.TONE_CDMA_NETWORK_BUSY, 2500);
|
||||||
toneGenerator.startTone(ToneGenerator.TONE_CDMA_NETWORK_BUSY, 2500);
|
|
||||||
this.destroyWithDelay(new DisconnectCause(DisconnectCause.BUSY, null), 2500);
|
this.destroyWithDelay(new DisconnectCause(DisconnectCause.BUSY, null), 2500);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -458,6 +457,17 @@ public class CallIntegration extends Connection {
|
||||||
Log.d(Config.LOGTAG, "destroyed!");
|
Log.d(Config.LOGTAG, "destroyed!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void startTone(final int volume, final int toneType, final int durationMs) {
|
||||||
|
final ToneGenerator toneGenerator;
|
||||||
|
try {
|
||||||
|
toneGenerator = new ToneGenerator(AudioManager.STREAM_VOICE_CALL, volume);
|
||||||
|
} catch (final RuntimeException e) {
|
||||||
|
Log.e(Config.LOGTAG, "could not initialize tone generator", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
toneGenerator.startTone(toneType, durationMs);
|
||||||
|
}
|
||||||
|
|
||||||
public static Uri address(final Jid contact) {
|
public static Uri address(final Jid contact) {
|
||||||
return Uri.parse(String.format("xmpp:%s", contact.toEscapedString()));
|
return Uri.parse(String.format("xmpp:%s", contact.toEscapedString()));
|
||||||
}
|
}
|
||||||
|
@ -532,6 +542,12 @@ public class CallIntegration extends Connection {
|
||||||
&& Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
|
&& Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
// we are relatively sure that old Oppo devices are broken too. We get reports of 'number
|
||||||
|
// not sent' from Oppo R15x (Android 10)
|
||||||
|
if ("OPPO".equalsIgnoreCase(Build.MANUFACTURER)
|
||||||
|
&& Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
// we only know of one Umidigi device (BISON_GT2_5G) that doesn't work (audio is not being
|
// we only know of one Umidigi device (BISON_GT2_5G) that doesn't work (audio is not being
|
||||||
// routed properly) However with those devices being extremely rare it's impossible to gauge
|
// routed properly) However with those devices being extremely rare it's impossible to gauge
|
||||||
// how many might be effected and no Naomi Wu around to clarify with the company directly
|
// how many might be effected and no Naomi Wu around to clarify with the company directly
|
||||||
|
|
|
@ -36,6 +36,7 @@ import eu.siacs.conversations.entities.Contact;
|
||||||
import eu.siacs.conversations.ui.RtpSessionActivity;
|
import eu.siacs.conversations.ui.RtpSessionActivity;
|
||||||
import eu.siacs.conversations.xmpp.Jid;
|
import eu.siacs.conversations.xmpp.Jid;
|
||||||
import eu.siacs.conversations.xmpp.jingle.AbstractJingleConnection;
|
import eu.siacs.conversations.xmpp.jingle.AbstractJingleConnection;
|
||||||
|
import eu.siacs.conversations.xmpp.jingle.JingleConnectionManager;
|
||||||
import eu.siacs.conversations.xmpp.jingle.JingleRtpConnection;
|
import eu.siacs.conversations.xmpp.jingle.JingleRtpConnection;
|
||||||
import eu.siacs.conversations.xmpp.jingle.Media;
|
import eu.siacs.conversations.xmpp.jingle.Media;
|
||||||
import eu.siacs.conversations.xmpp.jingle.RtpEndUserState;
|
import eu.siacs.conversations.xmpp.jingle.RtpEndUserState;
|
||||||
|
@ -126,9 +127,17 @@ public class CallIntegrationConnectionService extends ConnectionService {
|
||||||
// actually attempted
|
// actually attempted
|
||||||
// sendJingleFinishMessage(service, contact, Reason.CONNECTIVITY_ERROR);
|
// sendJingleFinishMessage(service, contact, Reason.CONNECTIVITY_ERROR);
|
||||||
} else {
|
} else {
|
||||||
final var proposal =
|
final JingleConnectionManager.RtpSessionProposal proposal;
|
||||||
service.getJingleConnectionManager()
|
try {
|
||||||
.proposeJingleRtpSession(account, with, media);
|
proposal =
|
||||||
|
service.getJingleConnectionManager()
|
||||||
|
.proposeJingleRtpSession(account, with, media);
|
||||||
|
} catch (final IllegalStateException e) {
|
||||||
|
return Connection.createFailedConnection(
|
||||||
|
new DisconnectCause(
|
||||||
|
DisconnectCause.ERROR,
|
||||||
|
"Phone is busy. Probably race condition. Try again in a moment"));
|
||||||
|
}
|
||||||
if (proposal == null) {
|
if (proposal == null) {
|
||||||
// TODO instead of just null checking try to get the sessionID
|
// TODO instead of just null checking try to get the sessionID
|
||||||
return Connection.createFailedConnection(
|
return Connection.createFailedConnection(
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
package eu.siacs.conversations.services;
|
package eu.siacs.conversations.services;
|
||||||
|
|
||||||
import static eu.siacs.conversations.utils.Random.SECURE_RANDOM;
|
|
||||||
|
|
||||||
import android.os.Build;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
@ -12,17 +10,15 @@ import com.google.common.cache.Cache;
|
||||||
import com.google.common.cache.CacheBuilder;
|
import com.google.common.cache.CacheBuilder;
|
||||||
|
|
||||||
import eu.siacs.conversations.Config;
|
import eu.siacs.conversations.Config;
|
||||||
import eu.siacs.conversations.crypto.TrustManagers;
|
|
||||||
import eu.siacs.conversations.entities.Account;
|
import eu.siacs.conversations.entities.Account;
|
||||||
import eu.siacs.conversations.entities.Room;
|
import eu.siacs.conversations.entities.Room;
|
||||||
import eu.siacs.conversations.http.HttpConnectionManager;
|
import eu.siacs.conversations.http.HttpConnectionManager;
|
||||||
import eu.siacs.conversations.http.services.MuclumbusService;
|
import eu.siacs.conversations.http.services.MuclumbusService;
|
||||||
import eu.siacs.conversations.parser.IqParser;
|
import eu.siacs.conversations.parser.IqParser;
|
||||||
import eu.siacs.conversations.utils.TLSSocketFactory;
|
|
||||||
import eu.siacs.conversations.xmpp.Jid;
|
import eu.siacs.conversations.xmpp.Jid;
|
||||||
import eu.siacs.conversations.xmpp.OnIqPacketReceived;
|
|
||||||
import eu.siacs.conversations.xmpp.XmppConnection;
|
import eu.siacs.conversations.xmpp.XmppConnection;
|
||||||
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
|
|
||||||
|
import im.conversations.android.xmpp.model.stanza.Iq;
|
||||||
|
|
||||||
import okhttp3.OkHttpClient;
|
import okhttp3.OkHttpClient;
|
||||||
import okhttp3.ResponseBody;
|
import okhttp3.ResponseBody;
|
||||||
|
@ -34,10 +30,6 @@ import retrofit2.Retrofit;
|
||||||
import retrofit2.converter.gson.GsonConverterFactory;
|
import retrofit2.converter.gson.GsonConverterFactory;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.KeyManagementException;
|
|
||||||
import java.security.KeyStoreException;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.cert.CertificateException;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
@ -47,9 +39,6 @@ import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import javax.net.ssl.SSLSocketFactory;
|
|
||||||
import javax.net.ssl.X509TrustManager;
|
|
||||||
|
|
||||||
public class ChannelDiscoveryService {
|
public class ChannelDiscoveryService {
|
||||||
|
|
||||||
private final XmppConnectionService service;
|
private final XmppConnectionService service;
|
||||||
|
@ -68,25 +57,7 @@ public class ChannelDiscoveryService {
|
||||||
this.muclumbusService = null;
|
this.muclumbusService = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final OkHttpClient.Builder builder = HttpConnectionManager.OK_HTTP_CLIENT.newBuilder();
|
final OkHttpClient.Builder builder = HttpConnectionManager.okHttpClient(service).newBuilder();
|
||||||
try {
|
|
||||||
final X509TrustManager trustManager;
|
|
||||||
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N) {
|
|
||||||
trustManager = TrustManagers.defaultWithBundledLetsEncrypt(service);
|
|
||||||
} else {
|
|
||||||
trustManager = TrustManagers.createDefaultTrustManager();
|
|
||||||
}
|
|
||||||
final SSLSocketFactory socketFactory =
|
|
||||||
new TLSSocketFactory(new X509TrustManager[] {trustManager}, SECURE_RANDOM);
|
|
||||||
builder.sslSocketFactory(socketFactory, trustManager);
|
|
||||||
} catch (final IOException
|
|
||||||
| KeyManagementException
|
|
||||||
| NoSuchAlgorithmException
|
|
||||||
| KeyStoreException
|
|
||||||
| CertificateException e) {
|
|
||||||
Log.d(Config.LOGTAG, "not reconfiguring service to work with bundled LetsEncrypt");
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
if (service.useTorToConnect()) {
|
if (service.useTorToConnect()) {
|
||||||
builder.proxy(HttpConnectionManager.getProxy());
|
builder.proxy(HttpConnectionManager.getProxy());
|
||||||
}
|
}
|
||||||
|
@ -203,7 +174,7 @@ public class ChannelDiscoveryService {
|
||||||
final String query, Map<Jid, Account> mucServices, final OnChannelSearchResultsFound listener) {
|
final String query, Map<Jid, Account> mucServices, final OnChannelSearchResultsFound listener) {
|
||||||
final Map<Jid, Account> localMucService = mucServices == null ? getLocalMucServices() : mucServices;
|
final Map<Jid, Account> localMucService = mucServices == null ? getLocalMucServices() : mucServices;
|
||||||
Log.d(Config.LOGTAG, "checking with " + localMucService.size() + " muc services");
|
Log.d(Config.LOGTAG, "checking with " + localMucService.size() + " muc services");
|
||||||
if (localMucService.size() == 0) {
|
if (localMucService.isEmpty()) {
|
||||||
listener.onChannelSearchResultsFound(Collections.emptyList());
|
listener.onChannelSearchResultsFound(Collections.emptyList());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -217,38 +188,37 @@ public class ChannelDiscoveryService {
|
||||||
}
|
}
|
||||||
final AtomicInteger queriesInFlight = new AtomicInteger();
|
final AtomicInteger queriesInFlight = new AtomicInteger();
|
||||||
final List<Room> rooms = new ArrayList<>();
|
final List<Room> rooms = new ArrayList<>();
|
||||||
for (Map.Entry<Jid, Account> entry : localMucService.entrySet()) {
|
for (final Map.Entry<Jid, Account> entry : localMucService.entrySet()) {
|
||||||
IqPacket itemsRequest = service.getIqGenerator().queryDiscoItems(entry.getKey());
|
Iq itemsRequest = service.getIqGenerator().queryDiscoItems(entry.getKey());
|
||||||
queriesInFlight.incrementAndGet();
|
queriesInFlight.incrementAndGet();
|
||||||
|
final var account = entry.getValue();
|
||||||
service.sendIqPacket(
|
service.sendIqPacket(
|
||||||
entry.getValue(),
|
account,
|
||||||
itemsRequest,
|
itemsRequest,
|
||||||
(account, itemsResponse) -> {
|
(itemsResponse) -> {
|
||||||
if (itemsResponse.getType() == IqPacket.TYPE.RESULT) {
|
if (itemsResponse.getType() == Iq.Type.RESULT) {
|
||||||
final List<Jid> items = IqParser.items(itemsResponse);
|
final List<Jid> items = IqParser.items(itemsResponse);
|
||||||
for (Jid item : items) {
|
for (final Jid item : items) {
|
||||||
if (item.isDomainJid()) continue; // Only looking for MUCs for now, and by spec they have a localpart
|
if (item.isDomainJid()) continue; // Only looking for MUCs for now, and by spec they have a localpart
|
||||||
IqPacket infoRequest =
|
final Iq infoRequest =
|
||||||
service.getIqGenerator().queryDiscoInfo(item);
|
service.getIqGenerator().queryDiscoInfo(item);
|
||||||
queriesInFlight.incrementAndGet();
|
queriesInFlight.incrementAndGet();
|
||||||
service.sendIqPacket(
|
service.sendIqPacket(
|
||||||
account,
|
account,
|
||||||
infoRequest,
|
infoRequest,
|
||||||
new OnIqPacketReceived() {
|
infoResponse -> {
|
||||||
@Override
|
if (infoResponse.getType()
|
||||||
public void onIqPacketReceived(
|
== Iq.Type.RESULT) {
|
||||||
Account account, IqPacket infoResponse) {
|
final Room room =
|
||||||
if (infoResponse.getType()
|
IqParser.parseRoom(infoResponse);
|
||||||
== IqPacket.TYPE.RESULT) {
|
if (room != null) {
|
||||||
final Room room =
|
rooms.add(room);
|
||||||
IqParser.parseRoom(infoResponse);
|
|
||||||
if (room != null) {
|
|
||||||
rooms.add(room);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (queriesInFlight.decrementAndGet() <= 0) {
|
if (queriesInFlight.decrementAndGet() <= 0) {
|
||||||
finishDiscoSearch(rooms, query, mucServices, listener);
|
finishDiscoSearch(rooms, query, mucServices, listener);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
queriesInFlight.decrementAndGet();
|
||||||
}
|
}
|
||||||
}, 20L);
|
}, 20L);
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,7 +41,6 @@ import android.util.Log;
|
||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.core.util.Consumer;
|
|
||||||
|
|
||||||
import com.google.common.base.Charsets;
|
import com.google.common.base.Charsets;
|
||||||
import com.google.common.base.Joiner;
|
import com.google.common.base.Joiner;
|
||||||
|
@ -86,6 +85,7 @@ import java.util.ArrayList;
|
||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
import java.util.function.Consumer;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
|
@ -23,8 +23,8 @@ import eu.siacs.conversations.xml.Element;
|
||||||
import eu.siacs.conversations.xmpp.Jid;
|
import eu.siacs.conversations.xmpp.Jid;
|
||||||
import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded;
|
import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded;
|
||||||
import eu.siacs.conversations.xmpp.mam.MamReference;
|
import eu.siacs.conversations.xmpp.mam.MamReference;
|
||||||
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
|
import im.conversations.android.xmpp.model.stanza.Iq;
|
||||||
import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
|
import im.conversations.android.xmpp.model.stanza.Message;
|
||||||
|
|
||||||
public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
|
public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
|
||||||
|
|
||||||
|
@ -81,7 +81,7 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Element findResult(MessagePacket packet) {
|
public static Element findResult(Message packet) {
|
||||||
for (Version version : values()) {
|
for (Version version : values()) {
|
||||||
Element result = packet.findChild("result", version.namespace);
|
Element result = packet.findChild("result", version.namespace);
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
|
@ -233,17 +233,17 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
|
||||||
throw new IllegalStateException("Attempted to run MAM query for archived conversation");
|
throw new IllegalStateException("Attempted to run MAM query for archived conversation");
|
||||||
}
|
}
|
||||||
Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() + ": running mam query " + query.toString());
|
Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() + ": running mam query " + query.toString());
|
||||||
final IqPacket packet = this.mXmppConnectionService.getIqGenerator().queryMessageArchiveManagement(query);
|
final Iq packet = this.mXmppConnectionService.getIqGenerator().queryMessageArchiveManagement(query);
|
||||||
this.mXmppConnectionService.sendIqPacket(account, packet, (a, p) -> {
|
this.mXmppConnectionService.sendIqPacket(account, packet, (p) -> {
|
||||||
final Element fin = p.findChild("fin", query.version.namespace);
|
final Element fin = p.findChild("fin", query.version.namespace);
|
||||||
if (p.getType() == IqPacket.TYPE.TIMEOUT) {
|
if (p.getType() == Iq.Type.TIMEOUT) {
|
||||||
synchronized (this.queries) {
|
synchronized (this.queries) {
|
||||||
this.queries.remove(query);
|
this.queries.remove(query);
|
||||||
if (query.hasCallback()) {
|
if (query.hasCallback()) {
|
||||||
query.callback(false);
|
query.callback(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (p.getType() == IqPacket.TYPE.RESULT && fin != null) {
|
} else if (p.getType() == Iq.Type.RESULT && fin != null) {
|
||||||
final boolean running;
|
final boolean running;
|
||||||
synchronized (this.queries) {
|
synchronized (this.queries) {
|
||||||
running = this.queries.contains(query);
|
running = this.queries.contains(query);
|
||||||
|
@ -253,10 +253,10 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
|
||||||
} else {
|
} else {
|
||||||
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": ignoring MAM iq result because query had been killed");
|
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": ignoring MAM iq result because query had been killed");
|
||||||
}
|
}
|
||||||
} else if (p.getType() == IqPacket.TYPE.RESULT && query.isLegacy()) {
|
} else if (p.getType() == Iq.Type.RESULT && query.isLegacy()) {
|
||||||
//do nothing
|
//do nothing
|
||||||
} else {
|
} else {
|
||||||
Log.d(Config.LOGTAG, a.getJid().asBareJid().toString() + ": error executing mam: " + p.toString());
|
Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() + ": error executing mam: " + p.toString());
|
||||||
try {
|
try {
|
||||||
finalizeQuery(query, true);
|
finalizeQuery(query, true);
|
||||||
} catch (final IllegalStateException e) {
|
} catch (final IllegalStateException e) {
|
||||||
|
@ -303,7 +303,7 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean inCatchup(Account account) {
|
public boolean inCatchup(Account account) {
|
||||||
synchronized (this.queries) {
|
synchronized (this.queries) {
|
||||||
for (Query query : queries) {
|
for (Query query : queries) {
|
||||||
if (query.account == account && query.isCatchup() && query.getWith() == null) {
|
if (query.account == account && query.isCatchup() && query.getWith() == null) {
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -270,6 +270,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
|
||||||
popupMenu.show();
|
popupMenu.show();
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
this.binding.editMucNameButton.setContentDescription(getString(R.string.edit_name_and_topic));
|
||||||
this.binding.editMucNameButton.setOnClickListener(this::onMucEditButtonClicked);
|
this.binding.editMucNameButton.setOnClickListener(this::onMucEditButtonClicked);
|
||||||
this.binding.mucEditTitle.addTextChangedListener(this);
|
this.binding.mucEditTitle.addTextChangedListener(this);
|
||||||
this.binding.mucEditSubject.addTextChangedListener(this);
|
this.binding.mucEditSubject.addTextChangedListener(this);
|
||||||
|
@ -432,6 +433,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
|
||||||
this.binding.mucEditor.setVisibility(View.GONE);
|
this.binding.mucEditor.setVisibility(View.GONE);
|
||||||
this.binding.mucDisplay.setVisibility(View.VISIBLE);
|
this.binding.mucDisplay.setVisibility(View.VISIBLE);
|
||||||
this.binding.editMucNameButton.setImageResource(R.drawable.ic_edit_24dp);
|
this.binding.editMucNameButton.setImageResource(R.drawable.ic_edit_24dp);
|
||||||
|
this.binding.editMucNameButton.setContentDescription(getString(R.string.edit_name_and_topic));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onMucInfoUpdated(String subject, String name) {
|
private void onMucInfoUpdated(String subject, String name) {
|
||||||
|
@ -669,7 +671,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
|
||||||
});
|
});
|
||||||
this.mUserPreviewAdapter.submitList(MucOptions.sub(users, GridManager.getCurrentColumnCount(binding.users)));
|
this.mUserPreviewAdapter.submitList(MucOptions.sub(users, GridManager.getCurrentColumnCount(binding.users)));
|
||||||
this.binding.invite.setVisibility(mucOptions.canInvite() ? View.VISIBLE : View.GONE);
|
this.binding.invite.setVisibility(mucOptions.canInvite() ? View.VISIBLE : View.GONE);
|
||||||
this.binding.showUsers.setVisibility(mucOptions.getUsers(true, mucOptions.getSelf().getAffiliation().ranks(MucOptions.Affiliation.ADMIN)).size() > 0 ? View.VISIBLE : View.GONE);
|
this.binding.showUsers.setVisibility(users.size() > 0 ? View.VISIBLE : View.GONE);
|
||||||
this.binding.showUsers.setText(getResources().getQuantityString(R.plurals.view_users, users.size(), users.size()));
|
this.binding.showUsers.setText(getResources().getQuantityString(R.plurals.view_users, users.size(), users.size()));
|
||||||
this.binding.usersWrapper.setVisibility(users.size() > 0 || mucOptions.canInvite() ? View.VISIBLE : View.GONE);
|
this.binding.usersWrapper.setVisibility(users.size() > 0 || mucOptions.canInvite() ? View.VISIBLE : View.GONE);
|
||||||
if (users.size() == 0) {
|
if (users.size() == 0) {
|
||||||
|
|
|
@ -217,7 +217,8 @@ import eu.siacs.conversations.xmpp.jingle.JingleFileTransferConnection;
|
||||||
import eu.siacs.conversations.xmpp.jingle.Media;
|
import eu.siacs.conversations.xmpp.jingle.Media;
|
||||||
import eu.siacs.conversations.xmpp.jingle.OngoingRtpSession;
|
import eu.siacs.conversations.xmpp.jingle.OngoingRtpSession;
|
||||||
import eu.siacs.conversations.xmpp.jingle.RtpCapability;
|
import eu.siacs.conversations.xmpp.jingle.RtpCapability;
|
||||||
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
|
|
||||||
|
import im.conversations.android.xmpp.model.stanza.Iq;
|
||||||
|
|
||||||
import me.drakeet.support.toast.ToastCompat;
|
import me.drakeet.support.toast.ToastCompat;
|
||||||
|
|
||||||
|
@ -3587,13 +3588,13 @@ public class ConversationFragment extends XmppFragment
|
||||||
} else {
|
} else {
|
||||||
if (!delayShow) conversation.showViewPager();
|
if (!delayShow) conversation.showViewPager();
|
||||||
binding.commandsViewProgressbar.setVisibility(View.VISIBLE);
|
binding.commandsViewProgressbar.setVisibility(View.VISIBLE);
|
||||||
activity.xmppConnectionService.fetchCommands(conversation.getAccount(), commandJid, (a, iq) -> {
|
activity.xmppConnectionService.fetchCommands(conversation.getAccount(), commandJid, (iq) -> {
|
||||||
if (activity == null) return;
|
if (activity == null) return;
|
||||||
|
|
||||||
activity.runOnUiThread(() -> {
|
activity.runOnUiThread(() -> {
|
||||||
binding.commandsViewProgressbar.setVisibility(View.GONE);
|
binding.commandsViewProgressbar.setVisibility(View.GONE);
|
||||||
commandAdapter.clear();
|
commandAdapter.clear();
|
||||||
if (iq.getType() == IqPacket.TYPE.RESULT) {
|
if (iq.getType() == Iq.Type.RESULT) {
|
||||||
for (Element child : iq.query().getChildren()) {
|
for (Element child : iq.query().getChildren()) {
|
||||||
if (!"item".equals(child.getName()) || !Namespace.DISCO_ITEMS.equals(child.getNamespace())) continue;
|
if (!"item".equals(child.getName()) || !Namespace.DISCO_ITEMS.equals(child.getNamespace())) continue;
|
||||||
commandAdapter.add(new CommandAdapter.Command0050(child));
|
commandAdapter.add(new CommandAdapter.Command0050(child));
|
||||||
|
|
|
@ -257,7 +257,8 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
|
||||||
if (ExceptionHelper.checkForCrash(this)) return;
|
if (ExceptionHelper.checkForCrash(this)) return;
|
||||||
if (offerToSetupDiallerIntegration()) return;
|
if (offerToSetupDiallerIntegration()) return;
|
||||||
if (offerToDownloadStickers()) return;
|
if (offerToDownloadStickers()) return;
|
||||||
openBatteryOptimizationDialogIfNeeded();
|
if (openBatteryOptimizationDialogIfNeeded()) return;
|
||||||
|
requestNotificationPermissionIfNeeded();
|
||||||
xmppConnectionService.rescanStickers();
|
xmppConnectionService.rescanStickers();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -282,7 +283,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
|
||||||
intent.setData(uri);
|
intent.setData(uri);
|
||||||
try {
|
try {
|
||||||
startActivityForResult(intent, REQUEST_BATTERY_OP);
|
startActivityForResult(intent, REQUEST_BATTERY_OP);
|
||||||
} catch (ActivityNotFoundException e) {
|
} catch (final ActivityNotFoundException e) {
|
||||||
Toast.makeText(this, R.string.device_does_not_support_battery_op, Toast.LENGTH_SHORT).show();
|
Toast.makeText(this, R.string.device_does_not_support_battery_op, Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -375,16 +376,16 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
|
||||||
|
|
||||||
private void notifyFragmentOfBackendConnected(@IdRes int id) {
|
private void notifyFragmentOfBackendConnected(@IdRes int id) {
|
||||||
final Fragment fragment = getFragmentManager().findFragmentById(id);
|
final Fragment fragment = getFragmentManager().findFragmentById(id);
|
||||||
if (fragment instanceof OnBackendConnected) {
|
if (fragment instanceof OnBackendConnected callback) {
|
||||||
((OnBackendConnected) fragment).onBackendConnected();
|
callback.onBackendConnected();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void refreshFragment(@IdRes int id) {
|
private void refreshFragment(@IdRes int id) {
|
||||||
final Fragment fragment = getFragmentManager().findFragmentById(id);
|
final Fragment fragment = getFragmentManager().findFragmentById(id);
|
||||||
if (fragment instanceof XmppFragment) {
|
if (fragment instanceof XmppFragment xmppFragment) {
|
||||||
((XmppFragment) fragment).refresh();
|
xmppFragment.refresh();
|
||||||
if (refreshForNewCaps) ((XmppFragment) fragment).refreshForNewCaps(newCapsJids);
|
if (refreshForNewCaps) xmppFragment.refreshForNewCaps(newCapsJids);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -47,6 +47,7 @@ import android.view.ViewGroup;
|
||||||
import android.widget.AdapterView.AdapterContextMenuInfo;
|
import android.widget.AdapterView.AdapterContextMenuInfo;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import androidx.databinding.DataBindingUtil;
|
import androidx.databinding.DataBindingUtil;
|
||||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
@ -96,42 +97,36 @@ public class ConversationsOverviewFragment extends XmppFragment {
|
||||||
private FragmentConversationsOverviewBinding binding;
|
private FragmentConversationsOverviewBinding binding;
|
||||||
private ConversationAdapter conversationsAdapter;
|
private ConversationAdapter conversationsAdapter;
|
||||||
private XmppActivity activity;
|
private XmppActivity activity;
|
||||||
private float mSwipeEscapeVelocity = 0f;
|
|
||||||
private final PendingActionHelper pendingActionHelper = new PendingActionHelper();
|
private final PendingActionHelper pendingActionHelper = new PendingActionHelper();
|
||||||
|
|
||||||
private final ItemTouchHelper.SimpleCallback callback = new ItemTouchHelper.SimpleCallback(0,LEFT|RIGHT) {
|
private final ItemTouchHelper.SimpleCallback callback = new ItemTouchHelper.SimpleCallback(0,LEFT|RIGHT) {
|
||||||
@Override
|
@Override
|
||||||
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
|
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
|
||||||
//todo maybe we can manually changing the position of the conversation
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public float getSwipeEscapeVelocity (float defaultValue) {
|
public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder,
|
||||||
return mSwipeEscapeVelocity;
|
float dX, float dY, int actionState, boolean isCurrentlyActive) {
|
||||||
}
|
if (viewHolder instanceof ConversationAdapter.ConversationViewHolder conversationViewHolder) {
|
||||||
|
getDefaultUIUtil().onDraw(c,recyclerView,conversationViewHolder.binding.frame,dX,dY,actionState,isCurrentlyActive);
|
||||||
@Override
|
|
||||||
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
|
|
||||||
float dX, float dY, int actionState, boolean isCurrentlyActive) {
|
|
||||||
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
|
|
||||||
if(actionState != ItemTouchHelper.ACTION_STATE_IDLE){
|
|
||||||
Paint paint = new Paint();
|
|
||||||
paint.setColor(MaterialColors.getColor(viewHolder.itemView, com.google.android.material.R.attr.colorSecondaryFixedDim));
|
|
||||||
paint.setStyle(Paint.Style.FILL);
|
|
||||||
c.drawRect(viewHolder.itemView.getLeft(),viewHolder.itemView.getTop()
|
|
||||||
,viewHolder.itemView.getRight(),viewHolder.itemView.getBottom(), paint);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
|
public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
|
||||||
super.clearView(recyclerView, viewHolder);
|
if (viewHolder instanceof ConversationAdapter.ConversationViewHolder conversationViewHolder) {
|
||||||
viewHolder.itemView.setAlpha(1f);
|
getDefaultUIUtil().clearView(conversationViewHolder.binding.frame);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
|
public float getSwipeEscapeVelocity(final float defaultEscapeVelocity) {
|
||||||
|
return 32 * defaultEscapeVelocity;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSwiped(final RecyclerView.ViewHolder viewHolder, final int direction) {
|
||||||
pendingActionHelper.execute();
|
pendingActionHelper.execute();
|
||||||
int position = viewHolder.getLayoutPosition();
|
int position = viewHolder.getLayoutPosition();
|
||||||
try {
|
try {
|
||||||
|
@ -291,7 +286,6 @@ public class ConversationsOverviewFragment extends XmppFragment {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(final LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
public View onCreateView(final LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
this.mSwipeEscapeVelocity = getResources().getDimension(R.dimen.swipe_escape_velocity);
|
|
||||||
this.binding = DataBindingUtil.inflate(inflater, R.layout.fragment_conversations_overview, container, false);
|
this.binding = DataBindingUtil.inflate(inflater, R.layout.fragment_conversations_overview, container, false);
|
||||||
this.binding.fab.setOnClickListener((view) -> StartConversationActivity.launch(getActivity()));
|
this.binding.fab.setOnClickListener((view) -> StartConversationActivity.launch(getActivity()));
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -34,6 +34,7 @@ import androidx.databinding.DataBindingUtil;
|
||||||
|
|
||||||
import com.google.common.base.Optional;
|
import com.google.common.base.Optional;
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.base.Strings;
|
||||||
import com.google.common.base.Throwables;
|
import com.google.common.base.Throwables;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
@ -363,10 +364,15 @@ public class RtpSessionActivity extends XmppActivity
|
||||||
|
|
||||||
private void acceptContentAdd() {
|
private void acceptContentAdd() {
|
||||||
try {
|
try {
|
||||||
requireRtpConnection()
|
final ContentAddition pendingContentAddition =
|
||||||
.acceptContentAdd(requireRtpConnection().getPendingContentAddition().summary);
|
requireRtpConnection().getPendingContentAddition();
|
||||||
|
if (pendingContentAddition == null) {
|
||||||
|
Log.d(Config.LOGTAG, "content offer was gone after granting permission");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
requireRtpConnection().acceptContentAdd(pendingContentAddition.summary);
|
||||||
} catch (final IllegalStateException e) {
|
} catch (final IllegalStateException e) {
|
||||||
Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
|
Toast.makeText(this, Strings.nullToEmpty(e.getMessage()), Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -537,7 +543,12 @@ public class RtpSessionActivity extends XmppActivity
|
||||||
final String action = intent.getAction();
|
final String action = intent.getAction();
|
||||||
Log.d(Config.LOGTAG, "initializeWithIntent(" + event + "," + action + ")");
|
Log.d(Config.LOGTAG, "initializeWithIntent(" + event + "," + action + ")");
|
||||||
final Account account = extractAccount(intent);
|
final Account account = extractAccount(intent);
|
||||||
final Jid with = Jid.ofEscaped(intent.getStringExtra(EXTRA_WITH));
|
final var extraWith = intent.getStringExtra(EXTRA_WITH);
|
||||||
|
final Jid with = Strings.isNullOrEmpty(extraWith) ? null : Jid.ofEscaped(extraWith);
|
||||||
|
if (with == null || account == null) {
|
||||||
|
Log.e(Config.LOGTAG, "intent is missing extras (account or with)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
final String sessionId = intent.getStringExtra(EXTRA_SESSION_ID);
|
final String sessionId = intent.getStringExtra(EXTRA_SESSION_ID);
|
||||||
if (sessionId != null) {
|
if (sessionId != null) {
|
||||||
if (initializeActivityWithRunningRtpSession(account, with, sessionId)) {
|
if (initializeActivityWithRunningRtpSession(account, with, sessionId)) {
|
||||||
|
@ -1089,16 +1100,21 @@ public class RtpSessionActivity extends XmppActivity
|
||||||
final CallIntegration.AudioDevice selectedAudioDevice, final int numberOfChoices) {
|
final CallIntegration.AudioDevice selectedAudioDevice, final int numberOfChoices) {
|
||||||
switch (selectedAudioDevice) {
|
switch (selectedAudioDevice) {
|
||||||
case EARPIECE -> {
|
case EARPIECE -> {
|
||||||
this.binding.inCallActionRight.setImageResource(
|
this.binding.inCallActionRight.setImageResource(R.drawable.ic_volume_off_24dp);
|
||||||
R.drawable.ic_volume_off_24dp);
|
|
||||||
if (numberOfChoices >= 2) {
|
if (numberOfChoices >= 2) {
|
||||||
|
this.binding.inCallActionRight.setContentDescription(
|
||||||
|
getString(R.string.call_is_using_earpiece_tap_to_switch_to_speaker));
|
||||||
this.binding.inCallActionRight.setOnClickListener(this::switchToSpeaker);
|
this.binding.inCallActionRight.setOnClickListener(this::switchToSpeaker);
|
||||||
} else {
|
} else {
|
||||||
|
this.binding.inCallActionRight.setContentDescription(
|
||||||
|
getString(R.string.call_is_using_earpiece));
|
||||||
this.binding.inCallActionRight.setOnClickListener(null);
|
this.binding.inCallActionRight.setOnClickListener(null);
|
||||||
this.binding.inCallActionRight.setClickable(false);
|
this.binding.inCallActionRight.setClickable(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case WIRED_HEADSET -> {
|
case WIRED_HEADSET -> {
|
||||||
|
this.binding.inCallActionRight.setContentDescription(
|
||||||
|
getString(R.string.call_is_using_wired_headset));
|
||||||
this.binding.inCallActionRight.setImageResource(R.drawable.ic_headset_mic_24dp);
|
this.binding.inCallActionRight.setImageResource(R.drawable.ic_headset_mic_24dp);
|
||||||
this.binding.inCallActionRight.setOnClickListener(null);
|
this.binding.inCallActionRight.setOnClickListener(null);
|
||||||
this.binding.inCallActionRight.setClickable(false);
|
this.binding.inCallActionRight.setClickable(false);
|
||||||
|
@ -1106,15 +1122,20 @@ public class RtpSessionActivity extends XmppActivity
|
||||||
case SPEAKER_PHONE -> {
|
case SPEAKER_PHONE -> {
|
||||||
this.binding.inCallActionRight.setImageResource(R.drawable.ic_volume_up_24dp);
|
this.binding.inCallActionRight.setImageResource(R.drawable.ic_volume_up_24dp);
|
||||||
if (numberOfChoices >= 2) {
|
if (numberOfChoices >= 2) {
|
||||||
|
this.binding.inCallActionRight.setContentDescription(
|
||||||
|
getString(R.string.call_is_using_speaker_tap_to_switch_to_earpiece));
|
||||||
this.binding.inCallActionRight.setOnClickListener(this::switchToEarpiece);
|
this.binding.inCallActionRight.setOnClickListener(this::switchToEarpiece);
|
||||||
} else {
|
} else {
|
||||||
|
this.binding.inCallActionRight.setContentDescription(
|
||||||
|
getString(R.string.call_is_using_speaker));
|
||||||
this.binding.inCallActionRight.setOnClickListener(null);
|
this.binding.inCallActionRight.setOnClickListener(null);
|
||||||
this.binding.inCallActionRight.setClickable(false);
|
this.binding.inCallActionRight.setClickable(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case BLUETOOTH -> {
|
case BLUETOOTH -> {
|
||||||
this.binding.inCallActionRight.setImageResource(
|
this.binding.inCallActionRight.setContentDescription(
|
||||||
R.drawable.ic_bluetooth_audio_24dp);
|
getString(R.string.call_is_using_bluetooth));
|
||||||
|
this.binding.inCallActionRight.setImageResource(R.drawable.ic_bluetooth_audio_24dp);
|
||||||
this.binding.inCallActionRight.setOnClickListener(null);
|
this.binding.inCallActionRight.setOnClickListener(null);
|
||||||
this.binding.inCallActionRight.setClickable(false);
|
this.binding.inCallActionRight.setClickable(false);
|
||||||
}
|
}
|
||||||
|
@ -1131,15 +1152,21 @@ public class RtpSessionActivity extends XmppActivity
|
||||||
R.drawable.ic_flip_camera_android_24dp);
|
R.drawable.ic_flip_camera_android_24dp);
|
||||||
this.binding.inCallActionFarRight.setVisibility(View.VISIBLE);
|
this.binding.inCallActionFarRight.setVisibility(View.VISIBLE);
|
||||||
this.binding.inCallActionFarRight.setOnClickListener(this::switchCamera);
|
this.binding.inCallActionFarRight.setOnClickListener(this::switchCamera);
|
||||||
|
this.binding.inCallActionFarRight.setContentDescription(
|
||||||
|
getString(R.string.flip_camera));
|
||||||
} else {
|
} else {
|
||||||
this.binding.inCallActionFarRight.setVisibility(View.GONE);
|
this.binding.inCallActionFarRight.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
if (videoEnabled) {
|
if (videoEnabled) {
|
||||||
this.binding.inCallActionRight.setImageResource(R.drawable.ic_videocam_24dp);
|
this.binding.inCallActionRight.setImageResource(R.drawable.ic_videocam_24dp);
|
||||||
this.binding.inCallActionRight.setOnClickListener(this::disableVideo);
|
this.binding.inCallActionRight.setOnClickListener(this::disableVideo);
|
||||||
|
this.binding.inCallActionRight.setContentDescription(
|
||||||
|
getString(R.string.video_is_enabled_tap_to_disable));
|
||||||
} else {
|
} else {
|
||||||
this.binding.inCallActionRight.setImageResource(R.drawable.ic_videocam_off_24dp);
|
this.binding.inCallActionRight.setImageResource(R.drawable.ic_videocam_off_24dp);
|
||||||
this.binding.inCallActionRight.setOnClickListener(this::enableVideo);
|
this.binding.inCallActionRight.setOnClickListener(this::enableVideo);
|
||||||
|
this.binding.inCallActionRight.setContentDescription(
|
||||||
|
getString(R.string.video_is_disabled_tap_to_enable));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1331,13 +1358,21 @@ public class RtpSessionActivity extends XmppActivity
|
||||||
}
|
}
|
||||||
|
|
||||||
private void switchToEarpiece(final View view) {
|
private void switchToEarpiece(final View view) {
|
||||||
requireCallIntegration().setAudioDevice(CallIntegration.AudioDevice.EARPIECE);
|
try {
|
||||||
acquireProximityWakeLock();
|
requireCallIntegration().setAudioDevice(CallIntegration.AudioDevice.EARPIECE);
|
||||||
|
acquireProximityWakeLock();
|
||||||
|
} catch (final IllegalStateException e) {
|
||||||
|
Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void switchToSpeaker(final View view) {
|
private void switchToSpeaker(final View view) {
|
||||||
requireCallIntegration().setAudioDevice(CallIntegration.AudioDevice.SPEAKER_PHONE);
|
try {
|
||||||
releaseProximityWakeLock();
|
requireCallIntegration().setAudioDevice(CallIntegration.AudioDevice.SPEAKER_PHONE);
|
||||||
|
releaseProximityWakeLock();
|
||||||
|
} catch (final IllegalStateException e) {
|
||||||
|
Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void retry(final View view) {
|
private void retry(final View view) {
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -68,9 +68,7 @@ public class UriHandlerActivity extends BaseActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void scan(final Activity activity, final boolean provisioning) {
|
public static void scan(final Activity activity, final boolean provisioning) {
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M
|
if (ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
|
||||||
|| ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA)
|
|
||||||
== PackageManager.PERMISSION_GRANTED) {
|
|
||||||
final Intent intent = new Intent(activity, UriHandlerActivity.class);
|
final Intent intent = new Intent(activity, UriHandlerActivity.class);
|
||||||
intent.setAction(UriHandlerActivity.ACTION_SCAN_QR_CODE);
|
intent.setAction(UriHandlerActivity.ACTION_SCAN_QR_CODE);
|
||||||
if (provisioning) {
|
if (provisioning) {
|
||||||
|
@ -114,6 +112,7 @@ public class UriHandlerActivity extends BaseActivity {
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
this.binding = DataBindingUtil.setContentView(this, R.layout.activity_uri_handler);
|
this.binding = DataBindingUtil.setContentView(this, R.layout.activity_uri_handler);
|
||||||
|
Activities.setStatusAndNavigationBarColors(this, binding.getRoot());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -187,7 +186,7 @@ public class UriHandlerActivity extends BaseActivity {
|
||||||
startActivity(intent);
|
startActivity(intent);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (accounts.size() == 0
|
if (accounts.isEmpty()
|
||||||
&& xmppUri.isAction(XmppUri.ACTION_ROSTER)
|
&& xmppUri.isAction(XmppUri.ACTION_ROSTER)
|
||||||
&& "y"
|
&& "y"
|
||||||
.equalsIgnoreCase(
|
.equalsIgnoreCase(
|
||||||
|
@ -203,7 +202,7 @@ public class UriHandlerActivity extends BaseActivity {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (accounts.size() == 0) {
|
if (accounts.isEmpty()) {
|
||||||
if (xmppUri.isValidJid()) {
|
if (xmppUri.isValidJid()) {
|
||||||
intent = SignupUtils.getSignUpIntent(this);
|
intent = SignupUtils.getSignUpIntent(this);
|
||||||
intent.putExtra(StartConversationActivity.EXTRA_INVITE_URI, xmppUri.toString());
|
intent.putExtra(StartConversationActivity.EXTRA_INVITE_URI, xmppUri.toString());
|
||||||
|
@ -259,14 +258,14 @@ public class UriHandlerActivity extends BaseActivity {
|
||||||
private void checkForLinkHeader(final HttpUrl url) {
|
private void checkForLinkHeader(final HttpUrl url) {
|
||||||
Log.d(Config.LOGTAG, "checking for link header on " + url);
|
Log.d(Config.LOGTAG, "checking for link header on " + url);
|
||||||
this.call =
|
this.call =
|
||||||
HttpConnectionManager.OK_HTTP_CLIENT.newCall(
|
HttpConnectionManager.okHttpClient(this).newCall(
|
||||||
new Request.Builder().url(url).head().build());
|
new Request.Builder().url(url).head().build());
|
||||||
this.call.enqueue(
|
this.call.enqueue(
|
||||||
new Callback() {
|
new Callback() {
|
||||||
@Override
|
@Override
|
||||||
public void onFailure(@NonNull Call call, @NonNull IOException e) {
|
public void onFailure(@NonNull Call call, @NonNull IOException e) {
|
||||||
Log.d(Config.LOGTAG, "unable to check HTTP url", e);
|
Log.d(Config.LOGTAG, "unable to check HTTP url", e);
|
||||||
showError(R.string.no_xmpp_adddress_found);
|
showErrorOnUiThread(R.string.no_xmpp_adddress_found);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -277,7 +276,7 @@ public class UriHandlerActivity extends BaseActivity {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
showError(R.string.no_xmpp_adddress_found);
|
showErrorOnUiThread(R.string.no_xmpp_adddress_found);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -301,6 +300,10 @@ public class UriHandlerActivity extends BaseActivity {
|
||||||
this.binding.error.setVisibility(View.VISIBLE);
|
this.binding.error.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void showErrorOnUiThread(@StringRes int error) {
|
||||||
|
runOnUiThread(()-> showError(error));
|
||||||
|
}
|
||||||
|
|
||||||
private static Class<?> findShareViaAccountClass() {
|
private static Class<?> findShareViaAccountClass() {
|
||||||
try {
|
try {
|
||||||
return Class.forName("eu.siacs.conversations.ui.ShareViaAccountActivity");
|
return Class.forName("eu.siacs.conversations.ui.ShareViaAccountActivity");
|
||||||
|
|
|
@ -523,14 +523,9 @@ public abstract class XmppActivity extends ActionBarActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean isOptimizingBattery() {
|
protected boolean isOptimizingBattery() {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
final PowerManager pm = getSystemService(PowerManager.class);
|
||||||
final PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
|
return !pm.isIgnoringBatteryOptimizations(getPackageName());
|
||||||
return pm != null
|
}
|
||||||
&& !pm.isIgnoringBatteryOptimizations(getPackageName());
|
|
||||||
} else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean isAffectedByDataSaver() {
|
protected boolean isAffectedByDataSaver() {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||||
|
|
1701
src/main/java/eu/siacs/conversations/ui/adapter/:w
Normal file
1701
src/main/java/eu/siacs/conversations/ui/adapter/:w
Normal file
File diff suppressed because it is too large
Load diff
|
@ -279,8 +279,8 @@ public class ConversationAdapter
|
||||||
void onConversationClick(View view, Conversation conversation);
|
void onConversationClick(View view, Conversation conversation);
|
||||||
}
|
}
|
||||||
|
|
||||||
static class ConversationViewHolder extends RecyclerView.ViewHolder {
|
public static class ConversationViewHolder extends RecyclerView.ViewHolder {
|
||||||
private final ItemConversationBinding binding;
|
public final ItemConversationBinding binding;
|
||||||
|
|
||||||
private ConversationViewHolder(final ItemConversationBinding binding) {
|
private ConversationViewHolder(final ItemConversationBinding binding) {
|
||||||
super(binding.getRoot());
|
super(binding.getRoot());
|
||||||
|
|
|
@ -45,6 +45,14 @@ public class MediaAdapter extends RecyclerView.Adapter<MediaAdapter.MediaViewHol
|
||||||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||||
"text/x-tex",
|
"text/x-tex",
|
||||||
"text/plain");
|
"text/plain");
|
||||||
|
|
||||||
|
private static final List<String> ARCHIVE_MIMES =
|
||||||
|
Arrays.asList(
|
||||||
|
"application/x-7z-compressed",
|
||||||
|
"application/zip",
|
||||||
|
"application/rar",
|
||||||
|
"application/x-gtar",
|
||||||
|
"application/x-tar");
|
||||||
public static final List<String> CODE_MIMES = Arrays.asList("text/html", "text/xml");
|
public static final List<String> CODE_MIMES = Arrays.asList("text/html", "text/xml");
|
||||||
|
|
||||||
private final ArrayList<Attachment> attachments = new ArrayList<>();
|
private final ArrayList<Attachment> attachments = new ArrayList<>();
|
||||||
|
@ -95,7 +103,7 @@ public class MediaAdapter extends RecyclerView.Adapter<MediaAdapter.MediaViewHol
|
||||||
return R.drawable.ic_person_48dp;
|
return R.drawable.ic_person_48dp;
|
||||||
} else if (mime.equals("application/vnd.android.package-archive")) {
|
} else if (mime.equals("application/vnd.android.package-archive")) {
|
||||||
return R.drawable.ic_adb_48dp;
|
return R.drawable.ic_adb_48dp;
|
||||||
} else if (mime.equals("application/zip") || mime.equals("application/rar")) {
|
} else if (ARCHIVE_MIMES.contains(mime)) {
|
||||||
return R.drawable.ic_archive_48dp;
|
return R.drawable.ic_archive_48dp;
|
||||||
} else if (mime.equals("application/epub+zip")
|
} else if (mime.equals("application/epub+zip")
|
||||||
|| mime.equals("application/vnd.amazon.mobi8-ebook")) {
|
|| mime.equals("application/vnd.amazon.mobi8-ebook")) {
|
||||||
|
|
|
@ -14,10 +14,13 @@ import androidx.activity.result.ActivityResultLauncher;
|
||||||
import androidx.activity.result.contract.ActivityResultContracts;
|
import androidx.activity.result.contract.ActivityResultContracts;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
|
import androidx.preference.ListPreference;
|
||||||
|
import androidx.preference.PreferenceFragmentCompat;
|
||||||
|
|
||||||
import de.monocles.chat.DownloadDefaultStickers;
|
import de.monocles.chat.DownloadDefaultStickers;
|
||||||
|
|
||||||
import eu.siacs.conversations.R;
|
import eu.siacs.conversations.R;
|
||||||
|
import eu.siacs.conversations.utils.UIHelper;
|
||||||
|
|
||||||
public class AttachmentsSettingsFragment extends XmppPreferenceFragment {
|
public class AttachmentsSettingsFragment extends XmppPreferenceFragment {
|
||||||
|
|
||||||
|
@ -75,6 +78,21 @@ public class AttachmentsSettingsFragment extends XmppPreferenceFragment {
|
||||||
runOnUiThread(() -> Toast.makeText(requireActivity(), "Blocked media will be displayed again", Toast.LENGTH_LONG).show());
|
runOnUiThread(() -> Toast.makeText(requireActivity(), "Blocked media will be displayed again", Toast.LENGTH_LONG).show());
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
final ListPreference autoAcceptFileSize = findPreference("auto_accept_file_size");
|
||||||
|
if (autoAcceptFileSize == null) {
|
||||||
|
throw new IllegalStateException("The preference resource file is missing preferences");
|
||||||
|
}
|
||||||
|
setValues(
|
||||||
|
autoAcceptFileSize,
|
||||||
|
R.array.file_size_values,
|
||||||
|
value -> {
|
||||||
|
if (value <= 0) {
|
||||||
|
return getString(R.string.never);
|
||||||
|
} else {
|
||||||
|
return UIHelper.filesizeToString(value);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void downloadStickers() {
|
protected void downloadStickers() {
|
||||||
|
|
|
@ -196,7 +196,12 @@ public class NotificationsSettingsFragment extends XmppPreferenceFragment {
|
||||||
uri = appSettings().getRingtone();
|
uri = appSettings().getRingtone();
|
||||||
}
|
}
|
||||||
Log.i(Config.LOGTAG, "current ringtone: " + uri);
|
Log.i(Config.LOGTAG, "current ringtone: " + uri);
|
||||||
this.pickRingtoneLauncher.launch(uri);
|
try {
|
||||||
|
this.pickRingtoneLauncher.launch(uri);
|
||||||
|
} catch (final ActivityNotFoundException e) {
|
||||||
|
Toast.makeText(requireActivity(), R.string.no_application_found, Toast.LENGTH_LONG)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private AppSettings appSettings() {
|
private AppSettings appSettings() {
|
||||||
|
|
|
@ -4,6 +4,7 @@ import android.content.Context;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.ArrayRes;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.preference.ListPreference;
|
import androidx.preference.ListPreference;
|
||||||
|
@ -13,6 +14,7 @@ import androidx.preference.Preference;
|
||||||
|
|
||||||
import com.rarepebble.colorpicker.ColorPreference;
|
import com.rarepebble.colorpicker.ColorPreference;
|
||||||
|
|
||||||
|
import com.google.common.base.Function;
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import com.google.common.primitives.Ints;
|
import com.google.common.primitives.Ints;
|
||||||
|
|
||||||
|
@ -111,14 +113,29 @@ public abstract class XmppPreferenceFragment extends PreferenceFragmentCompat {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static class TimeframeSummaryProvider
|
protected void setValues(
|
||||||
implements Preference.SummaryProvider<ListPreference> {
|
final ListPreference listPreference,
|
||||||
|
@ArrayRes int resId,
|
||||||
@Nullable
|
final Function<Integer, String> valueToName) {
|
||||||
@Override
|
final int[] choices = getResources().getIntArray(resId);
|
||||||
public CharSequence provideSummary(@NonNull ListPreference preference) {
|
final CharSequence[] entries = new CharSequence[choices.length];
|
||||||
final Integer value = Ints.tryParse(Strings.nullToEmpty(preference.getValue()));
|
final CharSequence[] entryValues = new CharSequence[choices.length];
|
||||||
return timeframeValueToName(preference.getContext(), value == null ? 0 : value);
|
for (int i = 0; i < choices.length; ++i) {
|
||||||
|
final int value = choices[i];
|
||||||
|
entryValues[i] = String.valueOf(choices[i]);
|
||||||
|
entries[i] = valueToName.apply(value);
|
||||||
}
|
}
|
||||||
|
listPreference.setEntries(entries);
|
||||||
|
listPreference.setEntryValues(entryValues);
|
||||||
|
listPreference.setSummaryProvider(
|
||||||
|
new Preference.SummaryProvider<ListPreference>() {
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public CharSequence provideSummary(@NonNull ListPreference preference) {
|
||||||
|
final Integer value =
|
||||||
|
Ints.tryParse(Strings.nullToEmpty(preference.getValue()));
|
||||||
|
return valueToName.apply(value == null ? 0 : value);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
package eu.siacs.conversations.utils;
|
package eu.siacs.conversations.utils;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.pm.PackageInfo;
|
|
||||||
import android.content.pm.PackageManager;
|
|
||||||
import android.content.pm.Signature;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||||
|
import com.google.common.base.Charsets;
|
||||||
|
import com.google.common.io.CharSink;
|
||||||
|
import com.google.common.io.Files;
|
||||||
|
|
||||||
import eu.siacs.conversations.AppSettings;
|
import eu.siacs.conversations.AppSettings;
|
||||||
import eu.siacs.conversations.Config;
|
import eu.siacs.conversations.Config;
|
||||||
|
@ -17,20 +17,16 @@ import eu.siacs.conversations.entities.Message;
|
||||||
import eu.siacs.conversations.services.XmppConnectionService;
|
import eu.siacs.conversations.services.XmppConnectionService;
|
||||||
import eu.siacs.conversations.ui.XmppActivity;
|
import eu.siacs.conversations.ui.XmppActivity;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.lang.ClassNotFoundException;
|
import java.lang.ClassNotFoundException;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.Date;
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
public class ExceptionHelper {
|
public class ExceptionHelper {
|
||||||
|
|
||||||
private static final String FILENAME = "stacktrace.txt";
|
private static final String FILENAME = "stacktrace.txt";
|
||||||
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH);
|
|
||||||
|
|
||||||
public static void init(final Context context) {
|
public static void init(final Context context) {
|
||||||
if (Thread.getDefaultUncaughtExceptionHandler() instanceof ExceptionHandler) {
|
if (Thread.getDefaultUncaughtExceptionHandler() instanceof ExceptionHandler) {
|
||||||
|
@ -45,72 +41,65 @@ public class ExceptionHelper {
|
||||||
return false;
|
return false;
|
||||||
} catch (final ClassNotFoundException e) { }
|
} catch (final ClassNotFoundException e) { }
|
||||||
|
|
||||||
try {
|
final XmppConnectionService service =
|
||||||
final XmppConnectionService service = activity == null ? null : activity.xmppConnectionService;
|
activity == null ? null : activity.xmppConnectionService;
|
||||||
if (service == null) {
|
if (service == null) {
|
||||||
return false;
|
|
||||||
}
|
|
||||||
final AppSettings appSettings = new AppSettings(activity);
|
|
||||||
if (!appSettings.isSendCrashReports() || Config.BUG_REPORTS == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
final Account account = AccountUtils.getFirstEnabled(service);
|
|
||||||
if (account == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
final FileInputStream file = activity.openFileInput(FILENAME);
|
|
||||||
final InputStreamReader inputStreamReader = new InputStreamReader(file);
|
|
||||||
final BufferedReader stacktrace = new BufferedReader(inputStreamReader);
|
|
||||||
final StringBuilder report = new StringBuilder();
|
|
||||||
final PackageManager pm = activity.getPackageManager();
|
|
||||||
final PackageInfo packageInfo;
|
|
||||||
try {
|
|
||||||
packageInfo = pm.getPackageInfo(activity.getPackageName(), PackageManager.GET_SIGNATURES);
|
|
||||||
final String versionName = packageInfo.versionName;
|
|
||||||
final int versionCode = packageInfo.versionCode;
|
|
||||||
final int version = versionCode > 10000 ? (versionCode / 100) : versionCode;
|
|
||||||
report.append(String.format(Locale.ROOT, "Version: %s(%d)", versionName, version)).append('\n');
|
|
||||||
report.append("Last Update: ").append(DATE_FORMAT.format(new Date(packageInfo.lastUpdateTime))).append('\n');
|
|
||||||
Signature[] signatures = packageInfo.signatures;
|
|
||||||
if (signatures != null && signatures.length >= 1) {
|
|
||||||
report.append("SHA-1: ").append(CryptoHelper.getFingerprintCert(packageInfo.signatures[0].toByteArray())).append('\n');
|
|
||||||
}
|
|
||||||
report.append('\n');
|
|
||||||
} catch (final Exception e) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
String line;
|
|
||||||
while ((line = stacktrace.readLine()) != null) {
|
|
||||||
report.append(line);
|
|
||||||
report.append('\n');
|
|
||||||
}
|
|
||||||
file.close();
|
|
||||||
activity.deleteFile(FILENAME);
|
|
||||||
final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(activity);
|
|
||||||
builder.setTitle(activity.getString(R.string.crash_report_title, activity.getString(R.string.app_name)));
|
|
||||||
builder.setMessage(activity.getString(R.string.crash_report_message, activity.getString(R.string.app_name)));
|
|
||||||
builder.setPositiveButton(activity.getText(R.string.send_now), (dialog, which) -> {
|
|
||||||
|
|
||||||
Log.d(Config.LOGTAG, "using account=" + account.getJid().asBareJid() + " to send in stack trace");
|
|
||||||
Conversation conversation = service.findOrCreateConversation(account, Config.BUG_REPORTS, false, true);
|
|
||||||
Message message = new Message(conversation, report.toString(), Message.ENCRYPTION_NONE);
|
|
||||||
service.sendMessage(message);
|
|
||||||
});
|
|
||||||
builder.setNegativeButton(activity.getText(R.string.send_never), (dialog, which) -> appSettings.setSendCrashReports(false));
|
|
||||||
builder.create().show();
|
|
||||||
return true;
|
|
||||||
} catch (final IOException ignored) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
final AppSettings appSettings = new AppSettings(activity);
|
||||||
|
if (!appSettings.isSendCrashReports() || Config.BUG_REPORTS == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final Account account = AccountUtils.getFirstEnabled(service);
|
||||||
|
if (account == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final var file = new File(activity.getCacheDir(), FILENAME);
|
||||||
|
if (!file.exists()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
final String report;
|
||||||
|
try {
|
||||||
|
report = Files.asCharSource(file, Charsets.UTF_8).read();
|
||||||
|
} catch (final IOException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (file.delete()) {
|
||||||
|
Log.d(Config.LOGTAG, "deleted crash report file");
|
||||||
|
}
|
||||||
|
final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(activity);
|
||||||
|
builder.setTitle(
|
||||||
|
activity.getString(
|
||||||
|
R.string.crash_report_title, activity.getString(R.string.app_name)));
|
||||||
|
builder.setMessage(
|
||||||
|
activity.getString(
|
||||||
|
R.string.crash_report_message, activity.getString(R.string.app_name)));
|
||||||
|
builder.setPositiveButton(
|
||||||
|
activity.getText(R.string.send_now),
|
||||||
|
(dialog, which) -> {
|
||||||
|
Log.d(
|
||||||
|
Config.LOGTAG,
|
||||||
|
"using account="
|
||||||
|
+ account.getJid().asBareJid()
|
||||||
|
+ " to send in stack trace");
|
||||||
|
Conversation conversation =
|
||||||
|
service.findOrCreateConversation(
|
||||||
|
account, Config.BUG_REPORTS, false, true);
|
||||||
|
Message message = new Message(conversation, report, Message.ENCRYPTION_NONE);
|
||||||
|
service.sendMessage(message);
|
||||||
|
});
|
||||||
|
builder.setNegativeButton(
|
||||||
|
activity.getText(R.string.send_never),
|
||||||
|
(dialog, which) -> appSettings.setSendCrashReports(false));
|
||||||
|
builder.create().show();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void writeToStacktraceFile(Context context, String msg) {
|
static void writeToStacktraceFile(final Context context, final String msg) {
|
||||||
try {
|
try {
|
||||||
OutputStream os = context.openFileOutput(FILENAME, Context.MODE_PRIVATE);
|
Files.asCharSink(new File(context.getCacheDir(), FILENAME), Charsets.UTF_8).write(msg);
|
||||||
os.write(msg.getBytes());
|
} catch (IOException e) {
|
||||||
os.flush();
|
Log.w(Config.LOGTAG, "could not write stack trace to file", e);
|
||||||
os.close();
|
|
||||||
} catch (IOException ignored) {
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -141,6 +141,7 @@ public final class MimeUtils {
|
||||||
add("application/vnd.sun.xml.writer.global", "sxg");
|
add("application/vnd.sun.xml.writer.global", "sxg");
|
||||||
add("application/vnd.sun.xml.writer.template", "stw");
|
add("application/vnd.sun.xml.writer.template", "stw");
|
||||||
add("application/vnd.visio", "vsd");
|
add("application/vnd.visio", "vsd");
|
||||||
|
add("application/x-7z-compressed","7z");
|
||||||
add("application/x-abiword", "abw");
|
add("application/x-abiword", "abw");
|
||||||
add("application/x-apple-diskimage", "dmg");
|
add("application/x-apple-diskimage", "dmg");
|
||||||
add("application/x-bcpio", "bcpio");
|
add("application/x-bcpio", "bcpio");
|
||||||
|
|
|
@ -6,24 +6,56 @@ import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import com.google.common.base.MoreObjects;
|
||||||
|
import com.google.common.base.Objects;
|
||||||
import com.google.common.base.Strings;
|
import com.google.common.base.Strings;
|
||||||
import com.google.common.base.Throwables;
|
import com.google.common.base.Throwables;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.collect.Ordering;
|
||||||
import com.google.common.net.InetAddresses;
|
import com.google.common.net.InetAddresses;
|
||||||
import com.google.common.primitives.Ints;
|
import com.google.common.primitives.Ints;
|
||||||
|
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.Config;
|
||||||
|
import eu.siacs.conversations.Conversations;
|
||||||
|
import eu.siacs.conversations.xmpp.Jid;
|
||||||
|
|
||||||
|
import org.minidns.dnsmessage.Question;
|
||||||
|
import org.minidns.dnsname.DnsName;
|
||||||
|
import org.minidns.dnsname.InvalidDnsNameException;
|
||||||
|
import org.minidns.dnsqueryresult.DnsQueryResult;
|
||||||
|
import org.minidns.record.A;
|
||||||
|
import org.minidns.record.AAAA;
|
||||||
|
import org.minidns.record.CNAME;
|
||||||
|
import org.minidns.record.Data;
|
||||||
|
import org.minidns.record.InternetAddressRR;
|
||||||
|
import org.minidns.record.Record;
|
||||||
|
import org.minidns.record.SRV;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.net.Inet4Address;
|
import java.net.Inet4Address;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.ExecutorService;
|
||||||
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.Config;
|
||||||
|
import eu.siacs.conversations.R;
|
||||||
|
import eu.siacs.conversations.services.XmppConnectionService;
|
||||||
|
import eu.siacs.conversations.xmpp.Jid;
|
||||||
|
|
||||||
//import de.gultsch.minidns.AndroidDNSClient;
|
|
||||||
import org.minidns.AbstractDnsClient;
|
import org.minidns.AbstractDnsClient;
|
||||||
import org.minidns.DnsCache;
|
import org.minidns.DnsCache;
|
||||||
import org.minidns.DnsClient;
|
import org.minidns.DnsClient;
|
||||||
|
@ -44,13 +76,35 @@ import org.minidns.record.Data;
|
||||||
import org.minidns.record.InternetAddressRR;
|
import org.minidns.record.InternetAddressRR;
|
||||||
import org.minidns.record.Record;
|
import org.minidns.record.Record;
|
||||||
import org.minidns.record.SRV;
|
import org.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;
|
|
||||||
|
|
||||||
public class Resolver {
|
public class Resolver {
|
||||||
|
|
||||||
|
private static final Comparator<Result> RESULT_COMPARATOR =
|
||||||
|
(left, right) -> {
|
||||||
|
if (left.priority == right.priority) {
|
||||||
|
if (left.directTls == right.directTls) {
|
||||||
|
if (left.ip == null && right.ip == null) {
|
||||||
|
return 0;
|
||||||
|
} else if (left.ip != null && right.ip != null) {
|
||||||
|
if (left.ip instanceof Inet4Address
|
||||||
|
&& right.ip instanceof Inet4Address) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
return left.ip instanceof Inet4Address ? -1 : 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return left.ip != null ? -1 : 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return left.directTls ? -1 : 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return left.priority - right.priority;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final ExecutorService DNS_QUERY_EXECUTOR = Executors.newFixedThreadPool(12);
|
||||||
|
|
||||||
public static final int DEFAULT_PORT_XMPP = 5222;
|
public static final int DEFAULT_PORT_XMPP = 5222;
|
||||||
|
|
||||||
private static final String DIRECT_TLS_SERVICE = "_xmpps-client";
|
private static final String DIRECT_TLS_SERVICE = "_xmpps-client";
|
||||||
|
@ -203,7 +257,7 @@ public class Resolver {
|
||||||
try {
|
try {
|
||||||
DnsName.from(hostname);
|
DnsName.from(hostname);
|
||||||
return false;
|
return false;
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (final InvalidDnsNameException | IllegalArgumentException e) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -224,206 +278,234 @@ public class Resolver {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static boolean useDirectTls(final int port) {
|
public static boolean useDirectTls(final int port) {
|
||||||
return port == 443 || port == 5223;
|
return port == 443 || port == 5223;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<Result> resolve(final String domain) {
|
public static List<Result> resolve(final String domain) {
|
||||||
final List<Result> ipResults = fromIpAddress(domain);
|
final List<Result> ipResults = fromIpAddress(domain);
|
||||||
if (ipResults.size() > 0) {
|
if (!ipResults.isEmpty()) {
|
||||||
return ipResults;
|
return ipResults;
|
||||||
}
|
}
|
||||||
final List<Result> results = new ArrayList<>();
|
|
||||||
final List<Result> fallbackResults = new ArrayList<>();
|
final var startTls = resolveSrvAsFuture(domain, false);
|
||||||
final Thread[] threads = new Thread[3];
|
final var directTls = resolveSrvAsFuture(domain, true);
|
||||||
threads[0] = new Thread(() -> {
|
|
||||||
try {
|
final var combined = merge(ImmutableList.of(startTls, directTls));
|
||||||
final List<Result> list = resolveSrv(domain, true);
|
|
||||||
synchronized (results) {
|
final var combinedWithFallback =
|
||||||
results.addAll(list);
|
Futures.transformAsync(
|
||||||
}
|
combined,
|
||||||
} catch (final Throwable throwable) {
|
results -> {
|
||||||
if (!(Throwables.getRootCause(throwable) instanceof InterruptedException)) {
|
if (results.isEmpty()) {
|
||||||
Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": error resolving SRV record (direct TLS)", throwable);
|
return resolveNoSrvAsFuture(DnsName.from(domain), true);
|
||||||
}
|
} else {
|
||||||
}
|
return Futures.immediateFuture(results);
|
||||||
});
|
}
|
||||||
threads[1] = new Thread(() -> {
|
},
|
||||||
try {
|
MoreExecutors.directExecutor());
|
||||||
final List<Result> list = resolveSrv(domain, false);
|
final var orderedFuture =
|
||||||
synchronized (results) {
|
Futures.transform(
|
||||||
results.addAll(list);
|
combinedWithFallback,
|
||||||
}
|
all -> Ordering.from(RESULT_COMPARATOR).immutableSortedCopy(all),
|
||||||
} catch (final Throwable throwable) {
|
MoreExecutors.directExecutor());
|
||||||
if (!(Throwables.getRootCause(throwable) instanceof InterruptedException)) {
|
|
||||||
Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": error resolving SRV record (STARTTLS)", throwable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
threads[2] = new Thread(() -> {
|
|
||||||
List<Result> list = resolveNoSrvRecords(DnsName.from(domain), true);
|
|
||||||
synchronized (fallbackResults) {
|
|
||||||
fallbackResults.addAll(list);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
for (final Thread thread : threads) {
|
|
||||||
thread.start();
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
threads[0].join();
|
final var ordered = orderedFuture.get();
|
||||||
threads[1].join();
|
Log.d(Config.LOGTAG, "Resolver (" + ordered.size() + "): " + ordered);
|
||||||
if (results.size() > 0) {
|
return ordered;
|
||||||
threads[2].interrupt();
|
} catch (final ExecutionException e) {
|
||||||
synchronized (results) {
|
Log.d(Config.LOGTAG, "error resolving DNS", e);
|
||||||
Collections.sort(results);
|
return Collections.emptyList();
|
||||||
Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": " + results);
|
} catch (final InterruptedException e) {
|
||||||
return results;
|
Log.d(Config.LOGTAG, "DNS resolution interrupted");
|
||||||
}
|
|
||||||
} else {
|
|
||||||
threads[2].join();
|
|
||||||
synchronized (fallbackResults) {
|
|
||||||
Collections.sort(fallbackResults);
|
|
||||||
Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": " + fallbackResults);
|
|
||||||
return fallbackResults;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
for (Thread thread : threads) {
|
|
||||||
thread.interrupt();
|
|
||||||
}
|
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<Result> fromIpAddress(String domain) {
|
private static List<Result> fromIpAddress(final String domain) {
|
||||||
if (!IP.matches(domain)) {
|
if (IP.matches(domain)) {
|
||||||
return Collections.emptyList();
|
final InetAddress inetAddress;
|
||||||
}
|
|
||||||
try {
|
|
||||||
Result result = new Result();
|
|
||||||
result.ip = InetAddress.getByName(domain);
|
|
||||||
result.port = DEFAULT_PORT_XMPP;
|
|
||||||
result.authenticated = true;
|
|
||||||
return Collections.singletonList(result);
|
|
||||||
} catch (UnknownHostException e) {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static List<Result> resolveSrv(String domain, final boolean directTls) throws IOException {
|
|
||||||
final String dnsNameS = (directTls ? DIRECT_TLS_SERVICE : STARTTLS_SERVICE) + "._tcp." + domain;
|
|
||||||
DnsName dnsName = DnsName.from(dnsNameS);
|
|
||||||
ResolverResult<SRV> result = resolveWithFallback(dnsName, SRV.class);
|
|
||||||
final List<Result> results = new ArrayList<>();
|
|
||||||
final List<Thread> threads = new ArrayList<>();
|
|
||||||
for (SRV record : result.getAnswersOrEmptySet()) {
|
|
||||||
if (record.name.length() == 0 && record.priority == 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
final boolean authentic = result.isAuthenticData() || record.target.toString().equals(knownSRV.get(dnsNameS));
|
|
||||||
threads.add(new Thread(() -> {
|
|
||||||
final List<Result> ipv4s = resolveIp(record, A.class, authentic, 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<Result> ipv6s = resolveIp(record, AAAA.class, authentic, directTls);
|
|
||||||
synchronized (results) {
|
|
||||||
results.addAll(ipv6s);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
for (Thread thread : threads) {
|
|
||||||
thread.start();
|
|
||||||
}
|
|
||||||
for (Thread thread : threads) {
|
|
||||||
try {
|
try {
|
||||||
thread.join();
|
inetAddress = InetAddress.getByName(domain);
|
||||||
} catch (InterruptedException e) {
|
} catch (final UnknownHostException e) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
|
final Result result = new Result();
|
||||||
|
result.ip = inetAddress;
|
||||||
|
result.port = DEFAULT_PORT_XMPP;
|
||||||
|
return Collections.singletonList(result);
|
||||||
|
} else {
|
||||||
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
return results;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <D extends InternetAddressRR> List<Result> resolveIp(SRV srv, Class<D> type, boolean authenticated, boolean directTls) {
|
private static ListenableFuture<List<Result>> resolveSrvAsFuture(
|
||||||
List<Result> list = new ArrayList<>();
|
final String domain, final boolean directTls) {
|
||||||
try {
|
final DnsName dnsName =
|
||||||
ResolverResult<D> results = resolveWithFallback(srv.target, type);
|
DnsName.from(
|
||||||
for (D record : results.getAnswersOrEmptySet()) {
|
(directTls ? DIRECT_TLS_SERVICE : STARTTLS_SERVICE) + "._tcp." + domain);
|
||||||
Result resolverResult = Result.fromRecord(srv, directTls);
|
final var resultFuture = resolveAsFuture(dnsName, SRV.class);
|
||||||
resolverResult.authenticated = results.isAuthenticData() && authenticated;
|
return Futures.transformAsync(
|
||||||
resolverResult.ip = record.getInetAddress();
|
resultFuture,
|
||||||
list.add(resolverResult);
|
result -> resolveIpsAsFuture(result, directTls),
|
||||||
}
|
MoreExecutors.directExecutor());
|
||||||
} catch (Throwable t) {
|
|
||||||
Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": error resolving " + type.getSimpleName() + " " + t.getMessage());
|
|
||||||
}
|
|
||||||
return list;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<Result> resolveNoSrvRecords(DnsName dnsName, boolean withCnames) {
|
@NonNull
|
||||||
final List<Result> results = new ArrayList<>();
|
private static ListenableFuture<List<Result>> resolveIpsAsFuture(
|
||||||
try {
|
final ResolverResult<SRV> srvResolverResult, final boolean directTls) {
|
||||||
ResolverResult<A> aResult = resolveWithFallback(dnsName, A.class);
|
final ImmutableList.Builder<ListenableFuture<List<Result>>> futuresBuilder =
|
||||||
for (A a : aResult.getAnswersOrEmptySet()) {
|
new ImmutableList.Builder<>();
|
||||||
Result r = Result.createDefault(dnsName, a.getInetAddress());
|
for (final SRV record : srvResolverResult.getAnswersOrEmptySet()) {
|
||||||
r.authenticated = aResult.isAuthenticData();
|
if (record.target.length() == 0 && record.priority == 0) {
|
||||||
results.add(r);
|
continue;
|
||||||
}
|
}
|
||||||
ResolverResult<AAAA> aaaaResult = resolveWithFallback(dnsName, AAAA.class);
|
final var ipv4sRaw =
|
||||||
for (AAAA aaaa : aaaaResult.getAnswersOrEmptySet()) {
|
resolveIpsAsFuture(
|
||||||
Result r = Result.createDefault(dnsName, aaaa.getInetAddress());
|
record, A.class, srvResolverResult.isAuthenticData(), directTls);
|
||||||
r.authenticated = aaaaResult.isAuthenticData();
|
final var ipv4s =
|
||||||
results.add(r);
|
Futures.transform(
|
||||||
}
|
ipv4sRaw,
|
||||||
if (results.size() == 0 && withCnames) {
|
results -> {
|
||||||
ResolverResult<CNAME> cnameResult = resolveWithFallback(dnsName, CNAME.class);
|
if (results.isEmpty()) {
|
||||||
for (CNAME cname : cnameResult.getAnswersOrEmptySet()) {
|
final Result resolverResult =
|
||||||
for (Result r : resolveNoSrvRecords(cname.name, false)) {
|
Result.fromRecord(record, directTls);
|
||||||
r.authenticated = r.authenticated && cnameResult.isAuthenticData();
|
resolverResult.authenticated =
|
||||||
results.add(r);
|
srvResolverResult.isAuthenticData();
|
||||||
|
return Collections.singletonList(resolverResult);
|
||||||
|
} else {
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MoreExecutors.directExecutor());
|
||||||
|
final var ipv6s =
|
||||||
|
resolveIpsAsFuture(
|
||||||
|
record, AAAA.class, srvResolverResult.isAuthenticData(), directTls);
|
||||||
|
futuresBuilder.add(ipv4s);
|
||||||
|
futuresBuilder.add(ipv6s);
|
||||||
|
}
|
||||||
|
final ImmutableList<ListenableFuture<List<Result>>> futures = futuresBuilder.build();
|
||||||
|
return merge(futures);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ListenableFuture<List<Result>> merge(
|
||||||
|
final Collection<ListenableFuture<List<Result>>> futures) {
|
||||||
|
return Futures.transform(
|
||||||
|
Futures.successfulAsList(futures),
|
||||||
|
lists -> {
|
||||||
|
final var builder = new ImmutableList.Builder<Result>();
|
||||||
|
for (final Collection<Result> list : lists) {
|
||||||
|
if (list == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
builder.addAll(list);
|
||||||
}
|
}
|
||||||
}
|
return builder.build();
|
||||||
}
|
},
|
||||||
} catch (final Throwable throwable) {
|
MoreExecutors.directExecutor());
|
||||||
if (!(Throwables.getRootCause(throwable) instanceof InterruptedException)) {
|
|
||||||
Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + "error resolving fallback records", throwable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
results.add(Result.createDefault(dnsName));
|
|
||||||
return results;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <D extends Data> ResolverResult<D> resolveWithFallback(DnsName dnsName, Class<D> type) throws IOException {
|
private static <D extends InternetAddressRR<?>>
|
||||||
final Question question = new Question(dnsName, Record.TYPE.getType(type));
|
ListenableFuture<List<Result>> resolveIpsAsFuture(
|
||||||
if (!DNSSECLESS_TLDS.contains(dnsName.getLabels()[0].toString())) {
|
final SRV srv, Class<D> type, boolean authenticated, boolean directTls) {
|
||||||
try {
|
final var resultFuture = resolveAsFuture(srv.target, type);
|
||||||
ResolverResult<D> result = DnssecResolverApi.INSTANCE.resolve(question);
|
return Futures.transform(
|
||||||
if (result.wasSuccessful() && !result.isAuthenticData()) {
|
resultFuture,
|
||||||
Log.d(Config.LOGTAG, "DNSSEC validation failed for " + type.getSimpleName() + " : " + result.getUnverifiedReasons());
|
result -> {
|
||||||
}
|
final var builder = new ImmutableList.Builder<Result>();
|
||||||
return result;
|
for (D record : result.getAnswersOrEmptySet()) {
|
||||||
} catch (DnssecValidationFailedException e) {
|
Result resolverResult = Result.fromRecord(srv, directTls);
|
||||||
Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": error resolving " + type.getSimpleName() + " with DNSSEC. Trying DNS instead.", e);
|
resolverResult.authenticated =
|
||||||
} catch (IOException e) {
|
result.isAuthenticData()
|
||||||
throw e;
|
&& authenticated; // TODO technically it does not matter if
|
||||||
} catch (Throwable throwable) {
|
// the IP
|
||||||
Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": error resolving " + type.getSimpleName() + " with DNSSEC. Trying DNS instead.", throwable);
|
// was authenticated
|
||||||
}
|
resolverResult.ip = record.getInetAddress();
|
||||||
}
|
builder.add(resolverResult);
|
||||||
return ResolverApi.INSTANCE.resolve(question);
|
}
|
||||||
|
return builder.build();
|
||||||
|
},
|
||||||
|
MoreExecutors.directExecutor());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Result implements Comparable<Result> {
|
private static ListenableFuture<List<Result>> resolveNoSrvAsFuture(
|
||||||
|
final DnsName dnsName, boolean cName) {
|
||||||
|
final ImmutableList.Builder<ListenableFuture<List<Result>>> futuresBuilder =
|
||||||
|
new ImmutableList.Builder<>();
|
||||||
|
ListenableFuture<List<Result>> aRecordResults =
|
||||||
|
Futures.transform(
|
||||||
|
resolveAsFuture(dnsName, A.class),
|
||||||
|
result ->
|
||||||
|
Lists.transform(
|
||||||
|
ImmutableList.copyOf(result.getAnswersOrEmptySet()),
|
||||||
|
a -> Result.createDefault(dnsName, a.getInetAddress(), result.isAuthenticData())),
|
||||||
|
MoreExecutors.directExecutor());
|
||||||
|
futuresBuilder.add(aRecordResults);
|
||||||
|
ListenableFuture<List<Result>> aaaaRecordResults =
|
||||||
|
Futures.transform(
|
||||||
|
resolveAsFuture(dnsName, AAAA.class),
|
||||||
|
result ->
|
||||||
|
Lists.transform(
|
||||||
|
ImmutableList.copyOf(result.getAnswersOrEmptySet()),
|
||||||
|
aaaa ->
|
||||||
|
Result.createDefault(
|
||||||
|
dnsName, aaaa.getInetAddress(), result.isAuthenticData())),
|
||||||
|
MoreExecutors.directExecutor());
|
||||||
|
futuresBuilder.add(aaaaRecordResults);
|
||||||
|
if (cName) {
|
||||||
|
ListenableFuture<List<Result>> cNameRecordResults =
|
||||||
|
Futures.transformAsync(
|
||||||
|
resolveAsFuture(dnsName, CNAME.class),
|
||||||
|
result -> {
|
||||||
|
Collection<ListenableFuture<List<Result>>> test =
|
||||||
|
Lists.transform(
|
||||||
|
ImmutableList.copyOf(result.getAnswersOrEmptySet()),
|
||||||
|
cname -> resolveNoSrvAsFuture(cname.target, false));
|
||||||
|
return merge(test);
|
||||||
|
},
|
||||||
|
MoreExecutors.directExecutor());
|
||||||
|
futuresBuilder.add(cNameRecordResults);
|
||||||
|
}
|
||||||
|
final ImmutableList<ListenableFuture<List<Result>>> futures = futuresBuilder.build();
|
||||||
|
final var noSrvFallbacks = merge(futures);
|
||||||
|
return Futures.transform(
|
||||||
|
noSrvFallbacks,
|
||||||
|
results -> {
|
||||||
|
if (results.isEmpty()) {
|
||||||
|
return Collections.singletonList(Result.createDefault(dnsName));
|
||||||
|
} else {
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
MoreExecutors.directExecutor());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <D extends Data> ListenableFuture<ResolverResult<D>> resolveAsFuture(
|
||||||
|
final DnsName dnsName, final Class<D> type) {
|
||||||
|
return Futures.submit(
|
||||||
|
() -> {
|
||||||
|
final Question question = new Question(dnsName, Record.TYPE.getType(type));
|
||||||
|
if (!DNSSECLESS_TLDS.contains(dnsName.getLabels()[0].toString())) {
|
||||||
|
try {
|
||||||
|
ResolverResult<D> result = DnssecResolverApi.INSTANCE.resolve(question);
|
||||||
|
if (result.wasSuccessful() && !result.isAuthenticData()) {
|
||||||
|
Log.d(Config.LOGTAG, "DNSSEC validation failed for " + type.getSimpleName() + " : " + result.getUnverifiedReasons());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} catch (DnssecValidationFailedException e) {
|
||||||
|
Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": error resolving " + type.getSimpleName() + " with DNSSEC. Trying DNS instead.", 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ResolverApi.INSTANCE.resolve(question);
|
||||||
|
},
|
||||||
|
DNS_QUERY_EXECUTOR);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Result {
|
||||||
public static final String DOMAIN = "domain";
|
public static final String DOMAIN = "domain";
|
||||||
public static final String IP = "ip";
|
public static final String IP = "ip";
|
||||||
public static final String HOSTNAME = "hostname";
|
public static final String HOSTNAME = "hostname";
|
||||||
|
@ -438,40 +520,42 @@ public class Resolver {
|
||||||
private boolean authenticated = false;
|
private boolean authenticated = false;
|
||||||
private int priority;
|
private int priority;
|
||||||
|
|
||||||
static Result fromRecord(SRV srv, boolean directTls) {
|
static Result fromRecord(final SRV srv, final boolean directTls) {
|
||||||
Result result = new Result();
|
final Result result = new Result();
|
||||||
result.port = srv.port;
|
result.port = srv.port;
|
||||||
result.hostname = srv.name;
|
result.hostname = srv.target;
|
||||||
result.directTls = directTls;
|
result.directTls = directTls;
|
||||||
result.priority = srv.priority;
|
result.priority = srv.priority;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Result createDefault(DnsName hostname, InetAddress ip) {
|
static Result createDefault(final DnsName hostname, final InetAddress ip, final boolean authenticated) {
|
||||||
Result result = new Result();
|
Result result = new Result();
|
||||||
result.port = DEFAULT_PORT_XMPP;
|
result.port = DEFAULT_PORT_XMPP;
|
||||||
result.hostname = hostname;
|
result.hostname = hostname;
|
||||||
result.ip = ip;
|
result.ip = ip;
|
||||||
|
result.authenticated = authenticated;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Result createDefault(DnsName hostname) {
|
static Result createDefault(final DnsName hostname) {
|
||||||
return createDefault(hostname, null);
|
return createDefault(hostname, null, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Result fromCursor(Cursor cursor) {
|
public static Result fromCursor(final Cursor cursor) {
|
||||||
final Result result = new Result();
|
final Result result = new Result();
|
||||||
try {
|
try {
|
||||||
result.ip = InetAddress.getByAddress(cursor.getBlob(cursor.getColumnIndex(IP)));
|
result.ip =
|
||||||
} catch (UnknownHostException e) {
|
InetAddress.getByAddress(cursor.getBlob(cursor.getColumnIndexOrThrow(IP)));
|
||||||
|
} catch (final UnknownHostException e) {
|
||||||
result.ip = null;
|
result.ip = null;
|
||||||
}
|
}
|
||||||
final String hostname = cursor.getString(cursor.getColumnIndex(HOSTNAME));
|
final String hostname = cursor.getString(cursor.getColumnIndexOrThrow(HOSTNAME));
|
||||||
result.hostname = hostname == null ? null : DnsName.from(hostname);
|
result.hostname = hostname == null ? null : DnsName.from(hostname);
|
||||||
result.port = cursor.getInt(cursor.getColumnIndex(PORT));
|
result.port = cursor.getInt(cursor.getColumnIndexOrThrow(PORT));
|
||||||
result.priority = cursor.getInt(cursor.getColumnIndex(PRIORITY));
|
result.priority = cursor.getInt(cursor.getColumnIndexOrThrow(PRIORITY));
|
||||||
result.authenticated = cursor.getInt(cursor.getColumnIndex(AUTHENTICATED)) > 0;
|
result.authenticated = cursor.getInt(cursor.getColumnIndexOrThrow(AUTHENTICATED)) > 0;
|
||||||
result.directTls = cursor.getInt(cursor.getColumnIndex(DIRECT_TLS)) > 0;
|
result.directTls = cursor.getInt(cursor.getColumnIndexOrThrow(DIRECT_TLS)) > 0;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -479,26 +563,18 @@ public class Resolver {
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
if (o == null || getClass() != o.getClass()) return false;
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
|
||||||
Result result = (Result) o;
|
Result result = (Result) o;
|
||||||
|
return port == result.port
|
||||||
if (port != result.port) return false;
|
&& directTls == result.directTls
|
||||||
if (directTls != result.directTls) return false;
|
&& authenticated == result.authenticated
|
||||||
if (authenticated != result.authenticated) return false;
|
&& priority == result.priority
|
||||||
if (priority != result.priority) return false;
|
&& Objects.equal(ip, result.ip)
|
||||||
if (ip != null ? !ip.equals(result.ip) : result.ip != null) return false;
|
&& Objects.equal(hostname, result.hostname);
|
||||||
return hostname != null ? hostname.equals(result.hostname) : result.hostname == null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
int result = ip != null ? ip.hashCode() : 0;
|
return Objects.hashCode(ip, hostname, port, directTls, authenticated, priority);
|
||||||
result = 31 * result + (hostname != null ? hostname.hashCode() : 0);
|
|
||||||
result = 31 * result + port;
|
|
||||||
result = 31 * result + (directTls ? 1 : 0);
|
|
||||||
result = 31 * result + (authenticated ? 1 : 0);
|
|
||||||
result = 31 * result + priority;
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public InetAddress getIp() {
|
public InetAddress getIp() {
|
||||||
|
@ -522,38 +598,16 @@ public class Resolver {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@NonNull
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "Result{" +
|
return MoreObjects.toStringHelper(this)
|
||||||
"ip='" + (ip == null ? null : ip.getHostAddress()) + '\'' +
|
.add("ip", ip)
|
||||||
", hostame='" + (hostname == null ? null : hostname.toString()) + '\'' +
|
.add("hostname", hostname)
|
||||||
", port=" + port +
|
.add("port", port)
|
||||||
", directTls=" + directTls +
|
.add("directTls", directTls)
|
||||||
", authenticated=" + authenticated +
|
.add("authenticated", authenticated)
|
||||||
", priority=" + priority +
|
.add("priority", priority)
|
||||||
'}';
|
.toString();
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compareTo(@NonNull Result result) {
|
|
||||||
if (result.priority == priority) {
|
|
||||||
if (directTls == result.directTls) {
|
|
||||||
if (ip == null && result.ip == null) {
|
|
||||||
return 0;
|
|
||||||
} else if (ip != null && result.ip != null) {
|
|
||||||
if (ip instanceof Inet4Address && result.ip instanceof Inet4Address) {
|
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
return ip instanceof Inet4Address ? -1 : 1;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return ip != null ? -1 : 1;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return directTls ? 1 : -1;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return priority - result.priority;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ContentValues toContentValues() {
|
public ContentValues toContentValues() {
|
||||||
|
@ -626,5 +680,4 @@ public class Resolver {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ import android.util.Log;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.core.app.NotificationCompat;
|
import androidx.core.app.NotificationCompat;
|
||||||
import androidx.work.ForegroundInfo;
|
import androidx.work.ForegroundInfo;
|
||||||
|
import androidx.work.WorkManager;
|
||||||
import androidx.work.Worker;
|
import androidx.work.Worker;
|
||||||
import androidx.work.WorkerParameters;
|
import androidx.work.WorkerParameters;
|
||||||
|
|
||||||
|
@ -35,7 +36,6 @@ import eu.siacs.conversations.entities.Conversation;
|
||||||
import eu.siacs.conversations.entities.Message;
|
import eu.siacs.conversations.entities.Message;
|
||||||
import eu.siacs.conversations.persistance.DatabaseBackend;
|
import eu.siacs.conversations.persistance.DatabaseBackend;
|
||||||
import eu.siacs.conversations.persistance.FileBackend;
|
import eu.siacs.conversations.persistance.FileBackend;
|
||||||
import eu.siacs.conversations.receiver.WorkManagerEventReceiver;
|
|
||||||
import eu.siacs.conversations.utils.BackupFileHeader;
|
import eu.siacs.conversations.utils.BackupFileHeader;
|
||||||
import eu.siacs.conversations.utils.Compatibility;
|
import eu.siacs.conversations.utils.Compatibility;
|
||||||
|
|
||||||
|
@ -99,6 +99,7 @@ public class ExportBackupWorker extends Worker {
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Result doWork() {
|
public Result doWork() {
|
||||||
|
setForegroundAsync(getForegroundInfo());
|
||||||
final List<File> files;
|
final List<File> files;
|
||||||
try {
|
try {
|
||||||
files = export();
|
files = export();
|
||||||
|
@ -227,18 +228,14 @@ public class ExportBackupWorker extends Worker {
|
||||||
IV,
|
IV,
|
||||||
salt);
|
salt);
|
||||||
final var notification = getNotification();
|
final var notification = getNotification();
|
||||||
if (!recurringBackup) {
|
final var cancelPendingIntent =
|
||||||
final var cancel = new Intent(context, WorkManagerEventReceiver.class);
|
WorkManager.getInstance(context).createCancelPendingIntent(getId());
|
||||||
cancel.setAction(WorkManagerEventReceiver.ACTION_STOP_BACKUP);
|
notification.addAction(
|
||||||
final var cancelPendingIntent =
|
new NotificationCompat.Action.Builder(
|
||||||
PendingIntent.getBroadcast(context, 197, cancel, PENDING_INTENT_FLAGS);
|
R.drawable.ic_cancel_24dp,
|
||||||
notification.addAction(
|
context.getString(R.string.cancel),
|
||||||
new NotificationCompat.Action.Builder(
|
cancelPendingIntent)
|
||||||
R.drawable.ic_cancel_24dp,
|
.build());
|
||||||
context.getString(R.string.cancel),
|
|
||||||
cancelPendingIntent)
|
|
||||||
.build());
|
|
||||||
}
|
|
||||||
final Progress progress = new Progress(notification, max, count);
|
final Progress progress = new Progress(notification, max, count);
|
||||||
final File directory = file.getParentFile();
|
final File directory = file.getParentFile();
|
||||||
if (directory != null && directory.mkdirs()) {
|
if (directory != null && directory.mkdirs()) {
|
||||||
|
|
|
@ -5,7 +5,9 @@ import androidx.annotation.NonNull;
|
||||||
import com.google.common.base.Optional;
|
import com.google.common.base.Optional;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.common.base.Strings;
|
||||||
import com.google.common.primitives.Ints;
|
import com.google.common.primitives.Ints;
|
||||||
|
import com.google.common.primitives.Longs;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
@ -16,7 +18,7 @@ import java.util.stream.Collectors;
|
||||||
import eu.siacs.conversations.utils.XmlHelper;
|
import eu.siacs.conversations.utils.XmlHelper;
|
||||||
import eu.siacs.conversations.xmpp.InvalidJid;
|
import eu.siacs.conversations.xmpp.InvalidJid;
|
||||||
import eu.siacs.conversations.xmpp.Jid;
|
import eu.siacs.conversations.xmpp.Jid;
|
||||||
import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
|
import im.conversations.android.xmpp.model.stanza.Message;
|
||||||
|
|
||||||
public class Element implements Node {
|
public class Element implements Node {
|
||||||
private final String name;
|
private final String name;
|
||||||
|
@ -141,6 +143,10 @@ public class Element implements Node {
|
||||||
return ImmutableList.copyOf(this.children);
|
return ImmutableList.copyOf(this.children);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setAttribute(final String name, final boolean value) {
|
||||||
|
this.setAttribute(name, value ? "1" : "0");
|
||||||
|
}
|
||||||
|
|
||||||
// Deprecated: you probably want bindTo or replaceChildren
|
// Deprecated: you probably want bindTo or replaceChildren
|
||||||
public Element setChildren(List<Element> children) {
|
public Element setChildren(List<Element> children) {
|
||||||
this.childNodes = new ArrayList(children);
|
this.childNodes = new ArrayList(children);
|
||||||
|
@ -165,6 +171,31 @@ public class Element implements Node {
|
||||||
return this.childNodes.stream().map(Node::getContent).filter(c -> c != null).collect(Collectors.joining());
|
return this.childNodes.stream().map(Node::getContent).filter(c -> c != null).collect(Collectors.joining());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long getLongAttribute(final String name) {
|
||||||
|
final var value = Longs.tryParse(Strings.nullToEmpty(this.attributes.get(name)));
|
||||||
|
return value == null ? 0 : value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<Integer> getOptionalIntAttribute(final String name) {
|
||||||
|
final String value = getAttribute(name);
|
||||||
|
if (value == null) {
|
||||||
|
return Optional.absent();
|
||||||
|
}
|
||||||
|
return Optional.fromNullable(Ints.tryParse(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Jid getAttributeAsJid(String name) {
|
||||||
|
final String jid = this.getAttribute(name);
|
||||||
|
if (jid != null && !jid.isEmpty()) {
|
||||||
|
try {
|
||||||
|
return Jid.ofEscaped(jid);
|
||||||
|
} catch (final IllegalArgumentException e) {
|
||||||
|
return InvalidJid.of(jid, this instanceof Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public Element setAttribute(String name, String value) {
|
public Element setAttribute(String name, String value) {
|
||||||
if (name != null && value != null) {
|
if (name != null && value != null) {
|
||||||
this.attributes.put(name, value);
|
this.attributes.put(name, value);
|
||||||
|
@ -224,7 +255,7 @@ public class Element implements Node {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Element removeAttribute(String name) {
|
public Element removeAttribute(final String name) {
|
||||||
this.attributes.remove(name);
|
this.attributes.remove(name);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
@ -242,26 +273,6 @@ public class Element implements Node {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<Integer> getOptionalIntAttribute(final String name) {
|
|
||||||
final String value = getAttribute(name);
|
|
||||||
if (value == null) {
|
|
||||||
return Optional.absent();
|
|
||||||
}
|
|
||||||
return Optional.fromNullable(Ints.tryParse(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
public Jid getAttributeAsJid(String name) {
|
|
||||||
final String jid = this.getAttribute(name);
|
|
||||||
if (jid != null && !jid.isEmpty()) {
|
|
||||||
try {
|
|
||||||
return Jid.ofEscaped(jid);
|
|
||||||
} catch (final IllegalArgumentException e) {
|
|
||||||
return InvalidJid.of(jid, this instanceof MessagePacket);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Hashtable<String, String> getAttributes() {
|
public Hashtable<String, String> getAttributes() {
|
||||||
return this.attributes;
|
return this.attributes;
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,7 @@ public class LocalizedContent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (contents.size() == 0) {
|
if (contents.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
final String userLanguage = Locale.getDefault().getLanguage();
|
final String userLanguage = Locale.getDefault().getLanguage();
|
||||||
|
|
|
@ -1,8 +1,29 @@
|
||||||
package eu.siacs.conversations.xml;
|
package eu.siacs.conversations.xml;
|
||||||
|
|
||||||
public final class Namespace {
|
public final class Namespace {
|
||||||
|
public static final String ADDRESSING = "http://jabber.org/protocol/address";
|
||||||
|
public static final String AXOLOTL = "eu.siacs.conversations.axolotl";
|
||||||
|
public static final String PGP_SIGNED = "jabber:x:signed";
|
||||||
|
public static final String PGP_ENCRYPTED = "jabber:x:encrypted";
|
||||||
|
public static final String AXOLOTL_BUNDLES = AXOLOTL + ".bundles";
|
||||||
|
public static final String AXOLOTL_DEVICE_LIST = AXOLOTL + ".devicelist";
|
||||||
|
public static final String HINTS = "urn:xmpp:hints";
|
||||||
|
public static final String MESSAGE_ARCHIVE_MANAGEMENT = "urn:xmpp:mam:2";
|
||||||
|
public static final String VERSION = "jabber:iq:version";
|
||||||
|
public static final String LAST_MESSAGE_CORRECTION = "urn:xmpp:message-correct:0";
|
||||||
|
public static final String RESULT_SET_MANAGEMENT = "http://jabber.org/protocol/rsm";
|
||||||
|
public static final String CHAT_MARKERS = "urn:xmpp:chat-markers:0";
|
||||||
|
public static final String CHAT_STATES = "http://jabber.org/protocol/chatstates";
|
||||||
|
public static final String DELIVERY_RECEIPTS = "urn:xmpp:receipts";
|
||||||
|
public static final String REACTIONS = "urn:xmpp:reactions:0";
|
||||||
|
public static final String VCARD_TEMP = "vcard-temp";
|
||||||
|
public static final String VCARD_TEMP_UPDATE = "vcard-temp:x:update";
|
||||||
|
public static final String DELAY = "urn:xmpp:delay";
|
||||||
|
public static final String OCCUPANT_ID = "urn:xmpp:occupant-id:0";
|
||||||
public static final String STREAMS = "http://etherx.jabber.org/streams";
|
public static final String STREAMS = "http://etherx.jabber.org/streams";
|
||||||
|
public static final String STANZAS = "urn:ietf:params:xml:ns:xmpp-stanzas";
|
||||||
public static final String JABBER_CLIENT = "jabber:client";
|
public static final String JABBER_CLIENT = "jabber:client";
|
||||||
|
public static final String FORWARD = "urn:xmpp:forward:0";
|
||||||
public static final String DISCO_ITEMS = "http://jabber.org/protocol/disco#items";
|
public static final String DISCO_ITEMS = "http://jabber.org/protocol/disco#items";
|
||||||
public static final String DISCO_INFO = "http://jabber.org/protocol/disco#info";
|
public static final String DISCO_INFO = "http://jabber.org/protocol/disco#info";
|
||||||
public static final String EXTERNAL_SERVICE_DISCOVERY = "urn:xmpp:extdisco:2";
|
public static final String EXTERNAL_SERVICE_DISCOVERY = "urn:xmpp:extdisco:2";
|
||||||
|
@ -23,12 +44,15 @@ public final class Namespace {
|
||||||
public static final String FAST = "urn:xmpp:fast:0";
|
public static final String FAST = "urn:xmpp:fast:0";
|
||||||
public static final String TLS = "urn:ietf:params:xml:ns:xmpp-tls";
|
public static final String TLS = "urn:ietf:params:xml:ns:xmpp-tls";
|
||||||
public static final String PUBSUB = "http://jabber.org/protocol/pubsub";
|
public static final String PUBSUB = "http://jabber.org/protocol/pubsub";
|
||||||
|
public static final String PUBSUB_EVENT = PUBSUB + "#event";
|
||||||
|
public static final String MUC = "http://jabber.org/protocol/muc";
|
||||||
public static final String PUBSUB_PUBLISH_OPTIONS = PUBSUB + "#publish-options";
|
public static final String PUBSUB_PUBLISH_OPTIONS = PUBSUB + "#publish-options";
|
||||||
public static final String PUBSUB_CONFIG_NODE_MAX = PUBSUB + "#config-node-max";
|
public static final String PUBSUB_CONFIG_NODE_MAX = PUBSUB + "#config-node-max";
|
||||||
public static final String PUBSUB_ERROR = PUBSUB + "#errors";
|
public static final String PUBSUB_ERROR = PUBSUB + "#errors";
|
||||||
public static final String PUBSUB_OWNER = PUBSUB + "#owner";
|
public static final String PUBSUB_OWNER = PUBSUB + "#owner";
|
||||||
public static final String NICK = "http://jabber.org/protocol/nick";
|
public static final String NICK = "http://jabber.org/protocol/nick";
|
||||||
public static final String FLEXIBLE_OFFLINE_MESSAGE_RETRIEVAL = "http://jabber.org/protocol/offline";
|
public static final String FLEXIBLE_OFFLINE_MESSAGE_RETRIEVAL =
|
||||||
|
"http://jabber.org/protocol/offline";
|
||||||
public static final String BIND = "urn:ietf:params:xml:ns:xmpp-bind";
|
public static final String BIND = "urn:ietf:params:xml:ns:xmpp-bind";
|
||||||
public static final String BIND2 = "urn:xmpp:bind:0";
|
public static final String BIND2 = "urn:xmpp:bind:0";
|
||||||
public static final String STREAM_MANAGEMENT = "urn:xmpp:sm:3";
|
public static final String STREAM_MANAGEMENT = "urn:xmpp:sm:3";
|
||||||
|
@ -38,7 +62,7 @@ public final class Namespace {
|
||||||
public static final String BOOKMARKS = "storage:bookmarks";
|
public static final String BOOKMARKS = "storage:bookmarks";
|
||||||
public static final String SYNCHRONIZATION = "im.quicksy.synchronization:0";
|
public static final String SYNCHRONIZATION = "im.quicksy.synchronization:0";
|
||||||
public static final String AVATAR_DATA = "urn:xmpp:avatar:data";
|
public static final String AVATAR_DATA = "urn:xmpp:avatar:data";
|
||||||
public static final String AVATAR_METADATA = "urn:xmpp:avatar:metadata";
|
public static final String AVATAR_METADATA = "urn:xmpp:avatar:metadata";
|
||||||
public static final String AVATAR_CONVERSION = "urn:xmpp:pep-vcard-conversion:0";
|
public static final String AVATAR_CONVERSION = "urn:xmpp:pep-vcard-conversion:0";
|
||||||
public static final String JINGLE = "urn:xmpp:jingle:1";
|
public static final String JINGLE = "urn:xmpp:jingle:1";
|
||||||
public static final String JINGLE_ERRORS = "urn:xmpp:jingle:errors:1";
|
public static final String JINGLE_ERRORS = "urn:xmpp:jingle:errors:1";
|
||||||
|
@ -48,7 +72,8 @@ public final class Namespace {
|
||||||
public static final String JINGLE_TRANSPORTS_S5B = "urn:xmpp:jingle:transports:s5b:1";
|
public static final String JINGLE_TRANSPORTS_S5B = "urn:xmpp:jingle:transports:s5b:1";
|
||||||
public static final String JINGLE_TRANSPORTS_IBB = "urn:xmpp:jingle:transports:ibb:1";
|
public static final String JINGLE_TRANSPORTS_IBB = "urn:xmpp:jingle:transports:ibb:1";
|
||||||
public static final String JINGLE_TRANSPORT_ICE_UDP = "urn:xmpp:jingle:transports:ice-udp:1";
|
public static final String JINGLE_TRANSPORT_ICE_UDP = "urn:xmpp:jingle:transports:ice-udp:1";
|
||||||
public static final String JINGLE_TRANSPORT_WEBRTC_DATA_CHANNEL = "urn:xmpp:jingle:transports:webrtc-datachannel:1";
|
public static final String JINGLE_TRANSPORT_WEBRTC_DATA_CHANNEL =
|
||||||
|
"urn:xmpp:jingle:transports:webrtc-datachannel:1";
|
||||||
public static final String JINGLE_TRANSPORT = "urn:xmpp:jingle:transports:dtls-sctp:1";
|
public static final String JINGLE_TRANSPORT = "urn:xmpp:jingle:transports:dtls-sctp:1";
|
||||||
public static final String JINGLE_APPS_RTP = "urn:xmpp:jingle:apps:rtp:1";
|
public static final String JINGLE_APPS_RTP = "urn:xmpp:jingle:apps:rtp:1";
|
||||||
|
|
||||||
|
@ -57,9 +82,12 @@ public final class Namespace {
|
||||||
public static final String JINGLE_APPS_GROUPING = "urn:xmpp:jingle:apps:grouping:0";
|
public static final String JINGLE_APPS_GROUPING = "urn:xmpp:jingle:apps:grouping:0";
|
||||||
public static final String JINGLE_FEATURE_AUDIO = "urn:xmpp:jingle:apps:rtp:audio";
|
public static final String JINGLE_FEATURE_AUDIO = "urn:xmpp:jingle:apps:rtp:audio";
|
||||||
public static final String JINGLE_FEATURE_VIDEO = "urn:xmpp:jingle:apps:rtp:video";
|
public static final String JINGLE_FEATURE_VIDEO = "urn:xmpp:jingle:apps:rtp:video";
|
||||||
public static final String JINGLE_RTP_HEADER_EXTENSIONS = "urn:xmpp:jingle:apps:rtp:rtp-hdrext:0";
|
public static final String JINGLE_RTP_HEADER_EXTENSIONS =
|
||||||
public static final String JINGLE_RTP_FEEDBACK_NEGOTIATION = "urn:xmpp:jingle:apps:rtp:rtcp-fb:0";
|
"urn:xmpp:jingle:apps:rtp:rtp-hdrext:0";
|
||||||
public static final String JINGLE_RTP_SOURCE_SPECIFIC_MEDIA_ATTRIBUTES = "urn:xmpp:jingle:apps:rtp:ssma:0";
|
public static final String JINGLE_RTP_FEEDBACK_NEGOTIATION =
|
||||||
|
"urn:xmpp:jingle:apps:rtp:rtcp-fb:0";
|
||||||
|
public static final String JINGLE_RTP_SOURCE_SPECIFIC_MEDIA_ATTRIBUTES =
|
||||||
|
"urn:xmpp:jingle:apps:rtp:ssma:0";
|
||||||
public static final String IBB = "http://jabber.org/protocol/ibb";
|
public static final String IBB = "http://jabber.org/protocol/ibb";
|
||||||
public static final String PING = "urn:xmpp:ping";
|
public static final String PING = "urn:xmpp:ping";
|
||||||
public static final String PUSH = "urn:xmpp:push:0";
|
public static final String PUSH = "urn:xmpp:push:0";
|
||||||
|
@ -70,8 +98,10 @@ public final class Namespace {
|
||||||
public static final String INVITE = "urn:xmpp:invite";
|
public static final String INVITE = "urn:xmpp:invite";
|
||||||
public static final String PARS = "urn:xmpp:pars:0";
|
public static final String PARS = "urn:xmpp:pars:0";
|
||||||
public static final String EASY_ONBOARDING_INVITE = "urn:xmpp:invite#invite";
|
public static final String EASY_ONBOARDING_INVITE = "urn:xmpp:invite#invite";
|
||||||
public static final String OMEMO_DTLS_SRTP_VERIFICATION = "http://gultsch.de/xmpp/drafts/omemo/dlts-srtp-verification";
|
public static final String OMEMO_DTLS_SRTP_VERIFICATION =
|
||||||
public static final String JINGLE_TRANSPORT_ICE_OPTION = "http://gultsch.de/xmpp/drafts/jingle/transports/ice-udp/option";
|
"http://gultsch.de/xmpp/drafts/omemo/dlts-srtp-verification";
|
||||||
|
public static final String JINGLE_TRANSPORT_ICE_OPTION =
|
||||||
|
"http://gultsch.de/xmpp/drafts/jingle/transports/ice-udp/option";
|
||||||
public static final String UNIFIED_PUSH = "http://gultsch.de/xmpp/drafts/unified-push";
|
public static final String UNIFIED_PUSH = "http://gultsch.de/xmpp/drafts/unified-push";
|
||||||
public static final String VCARD4 = "urn:ietf:params:xml:ns:vcard-4.0";
|
public static final String VCARD4 = "urn:ietf:params:xml:ns:vcard-4.0";
|
||||||
public static final String REPORTING = "urn:xmpp:reporting:1";
|
public static final String REPORTING = "urn:xmpp:reporting:1";
|
||||||
|
@ -80,4 +110,7 @@ public final class Namespace {
|
||||||
public static final String HASHES = "urn:xmpp:hashes:2";
|
public static final String HASHES = "urn:xmpp:hashes:2";
|
||||||
public static final String MDS_DISPLAYED = "urn:xmpp:mds:displayed:0";
|
public static final String MDS_DISPLAYED = "urn:xmpp:mds:displayed:0";
|
||||||
public static final String MDS_SERVER_ASSIST = "urn:xmpp:mds:server-assist:0";
|
public static final String MDS_SERVER_ASSIST = "urn:xmpp:mds:server-assist:0";
|
||||||
|
|
||||||
|
public static final String ENTITY_CAPABILITIES = "http://jabber.org/protocol/caps";
|
||||||
|
public static final String ENTITY_CAPABILITIES_2 = "urn:xmpp:caps";
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,12 @@ package eu.siacs.conversations.xml;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.Xml;
|
import android.util.Xml;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.Config;
|
||||||
|
|
||||||
|
import im.conversations.android.xmpp.ExtensionFactory;
|
||||||
|
import im.conversations.android.xmpp.model.Extension;
|
||||||
|
import im.conversations.android.xmpp.model.StreamElement;
|
||||||
|
|
||||||
import org.xmlpull.v1.XmlPullParser;
|
import org.xmlpull.v1.XmlPullParser;
|
||||||
import org.xmlpull.v1.XmlPullParserException;
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
|
|
||||||
|
@ -11,8 +17,6 @@ import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
|
|
||||||
import eu.siacs.conversations.Config;
|
|
||||||
|
|
||||||
public class XmlReader implements Closeable {
|
public class XmlReader implements Closeable {
|
||||||
private final XmlPullParser parser;
|
private final XmlPullParser parser;
|
||||||
private InputStream is;
|
private InputStream is;
|
||||||
|
@ -90,8 +94,21 @@ public class XmlReader implements Closeable {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Element readElement(Tag currentTag) throws IOException {
|
public <T extends StreamElement> T readElement(final Tag current, final Class<T> clazz)
|
||||||
Element element = new Element(currentTag.getName());
|
throws IOException {
|
||||||
|
final Element element = readElement(current);
|
||||||
|
if (clazz.isInstance(element)) {
|
||||||
|
return clazz.cast(element);
|
||||||
|
}
|
||||||
|
throw new IOException(
|
||||||
|
String.format("Read unexpected {%s}%s", element.getNamespace(), element.getName()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public Element readElement(final Tag currentTag) throws IOException {
|
||||||
|
final var attributes = currentTag.getAttributes();
|
||||||
|
final var namespace = attributes.get("xmlns");
|
||||||
|
final var name = currentTag.getName();
|
||||||
|
final Element element = ExtensionFactory.create(name, namespace);
|
||||||
element.setAttributes(currentTag.getAttributes());
|
element.setAttributes(currentTag.getAttributes());
|
||||||
Tag nextTag = this.readTag();
|
Tag nextTag = this.readTag();
|
||||||
if (nextTag == null) {
|
if (nextTag == null) {
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -59,8 +59,8 @@ public class Data extends Element {
|
||||||
field.setValues(values);
|
field.setValues(values);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void submit(Bundle options) {
|
public void submit(final Bundle options) {
|
||||||
for (Field field : getFields()) {
|
for (final Field field : getFields()) {
|
||||||
if (options.containsKey(field.getFieldName())) {
|
if (options.containsKey(field.getFieldName())) {
|
||||||
field.setValue(options.getString(field.getFieldName()));
|
field.setValue(options.getString(field.getFieldName()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,14 +34,13 @@ import eu.siacs.conversations.xmpp.Jid;
|
||||||
import eu.siacs.conversations.xmpp.XmppConnection;
|
import eu.siacs.conversations.xmpp.XmppConnection;
|
||||||
import eu.siacs.conversations.xmpp.jingle.stanzas.Content;
|
import eu.siacs.conversations.xmpp.jingle.stanzas.Content;
|
||||||
import eu.siacs.conversations.xmpp.jingle.stanzas.GenericDescription;
|
import eu.siacs.conversations.xmpp.jingle.stanzas.GenericDescription;
|
||||||
import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket;
|
|
||||||
import eu.siacs.conversations.xmpp.jingle.stanzas.Propose;
|
import eu.siacs.conversations.xmpp.jingle.stanzas.Propose;
|
||||||
import eu.siacs.conversations.xmpp.jingle.stanzas.Reason;
|
import eu.siacs.conversations.xmpp.jingle.stanzas.Reason;
|
||||||
import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription;
|
import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription;
|
||||||
import eu.siacs.conversations.xmpp.jingle.transports.InbandBytestreamsTransport;
|
import eu.siacs.conversations.xmpp.jingle.transports.InbandBytestreamsTransport;
|
||||||
import eu.siacs.conversations.xmpp.jingle.transports.Transport;
|
import eu.siacs.conversations.xmpp.jingle.transports.Transport;
|
||||||
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
|
import im.conversations.android.xmpp.model.jingle.Jingle;
|
||||||
import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
|
import im.conversations.android.xmpp.model.stanza.Iq;
|
||||||
|
|
||||||
import java.lang.ref.WeakReference;
|
import java.lang.ref.WeakReference;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
|
@ -77,9 +76,11 @@ public class JingleConnectionManager extends AbstractConnectionManager {
|
||||||
return Base64.encodeToString(id, Base64.NO_WRAP | Base64.NO_PADDING | Base64.URL_SAFE);
|
return Base64.encodeToString(id, Base64.NO_WRAP | Base64.NO_PADDING | Base64.URL_SAFE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void deliverPacket(final Account account, final JinglePacket packet) {
|
public void deliverPacket(final Account account, final Iq packet) {
|
||||||
final String sessionId = packet.getSessionId();
|
final var jingle = packet.getExtension(Jingle.class);
|
||||||
final JinglePacket.Action action = packet.getAction();
|
Preconditions.checkNotNull(jingle,"Passed iq packet w/o jingle extension to Connection Manager");
|
||||||
|
final String sessionId = jingle.getSessionId();
|
||||||
|
final Jingle.Action action = jingle.getAction();
|
||||||
if (sessionId == null) {
|
if (sessionId == null) {
|
||||||
respondWithJingleError(account, packet, "unknown-session", "item-not-found", "cancel");
|
respondWithJingleError(account, packet, "unknown-session", "item-not-found", "cancel");
|
||||||
return;
|
return;
|
||||||
|
@ -88,13 +89,13 @@ public class JingleConnectionManager extends AbstractConnectionManager {
|
||||||
respondWithJingleError(account, packet, null, "bad-request", "cancel");
|
respondWithJingleError(account, packet, null, "bad-request", "cancel");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final AbstractJingleConnection.Id id = AbstractJingleConnection.Id.of(account, packet);
|
final AbstractJingleConnection.Id id = AbstractJingleConnection.Id.of(account, packet, jingle);
|
||||||
final AbstractJingleConnection existingJingleConnection = connections.get(id);
|
final AbstractJingleConnection existingJingleConnection = connections.get(id);
|
||||||
if (existingJingleConnection != null) {
|
if (existingJingleConnection != null) {
|
||||||
existingJingleConnection.deliverPacket(packet);
|
existingJingleConnection.deliverPacket(packet);
|
||||||
} else if (action == JinglePacket.Action.SESSION_INITIATE) {
|
} else if (action == Jingle.Action.SESSION_INITIATE) {
|
||||||
final Jid from = packet.getFrom();
|
final Jid from = packet.getFrom();
|
||||||
final Content content = packet.getJingleContent();
|
final Content content = jingle.getJingleContent();
|
||||||
final String descriptionNamespace =
|
final String descriptionNamespace =
|
||||||
content == null ? null : content.getDescriptionNamespace();
|
content == null ? null : content.getDescriptionNamespace();
|
||||||
final AbstractJingleConnection connection;
|
final AbstractJingleConnection connection;
|
||||||
|
@ -165,14 +166,14 @@ public class JingleConnectionManager extends AbstractConnectionManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendSessionTerminate(
|
private void sendSessionTerminate(
|
||||||
final Account account, final IqPacket request, final AbstractJingleConnection.Id id) {
|
final Account account, final Iq request, final AbstractJingleConnection.Id id) {
|
||||||
mXmppConnectionService.sendIqPacket(
|
mXmppConnectionService.sendIqPacket(
|
||||||
account, request.generateResponse(IqPacket.TYPE.RESULT), null);
|
account, request.generateResponse(Iq.Type.RESULT), null);
|
||||||
final JinglePacket sessionTermination =
|
final var iq = new Iq(Iq.Type.SET);
|
||||||
new JinglePacket(JinglePacket.Action.SESSION_TERMINATE, id.sessionId);
|
iq.setTo(id.with);
|
||||||
sessionTermination.setTo(id.with);
|
final var sessionTermination = iq.addExtension(new Jingle(Jingle.Action.SESSION_TERMINATE, id.sessionId));
|
||||||
sessionTermination.setReason(Reason.BUSY, null);
|
sessionTermination.setReason(Reason.BUSY, null);
|
||||||
mXmppConnectionService.sendIqPacket(account, sessionTermination, null);
|
mXmppConnectionService.sendIqPacket(account, iq, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isUsingClearNet(final Account account) {
|
private boolean isUsingClearNet(final Account account) {
|
||||||
|
@ -265,11 +266,11 @@ public class JingleConnectionManager extends AbstractConnectionManager {
|
||||||
|
|
||||||
void respondWithJingleError(
|
void respondWithJingleError(
|
||||||
final Account account,
|
final Account account,
|
||||||
final IqPacket original,
|
final Iq original,
|
||||||
final String jingleCondition,
|
final String jingleCondition,
|
||||||
final String condition,
|
final String condition,
|
||||||
final String conditionType) {
|
final String conditionType) {
|
||||||
final IqPacket response = original.generateResponse(IqPacket.TYPE.ERROR);
|
final Iq response = original.generateResponse(Iq.Type.ERROR);
|
||||||
final Element error = response.addChild("error");
|
final Element error = response.addChild("error");
|
||||||
error.setAttribute("type", conditionType);
|
error.setAttribute("type", conditionType);
|
||||||
error.addChild(condition, "urn:ietf:params:xml:ns:xmpp-stanzas");
|
error.addChild(condition, "urn:ietf:params:xml:ns:xmpp-stanzas");
|
||||||
|
@ -440,7 +441,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
|
||||||
final int activeDevices = account.activeDevicesWithRtpCapability();
|
final int activeDevices = account.activeDevicesWithRtpCapability();
|
||||||
Log.d(Config.LOGTAG, "active devices with rtp capability: " + activeDevices);
|
Log.d(Config.LOGTAG, "active devices with rtp capability: " + activeDevices);
|
||||||
if (activeDevices == 0) {
|
if (activeDevices == 0) {
|
||||||
final MessagePacket reject =
|
final var reject =
|
||||||
mXmppConnectionService
|
mXmppConnectionService
|
||||||
.getMessageGenerator()
|
.getMessageGenerator()
|
||||||
.sessionReject(from, sessionId);
|
.sessionReject(from, sessionId);
|
||||||
|
@ -494,10 +495,11 @@ public class JingleConnectionManager extends AbstractConnectionManager {
|
||||||
if (remoteMsgId == null) {
|
if (remoteMsgId == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final MessagePacket errorMessage = new MessagePacket();
|
final var errorMessage =
|
||||||
|
new im.conversations.android.xmpp.model.stanza.Message();
|
||||||
errorMessage.setTo(from);
|
errorMessage.setTo(from);
|
||||||
errorMessage.setId(remoteMsgId);
|
errorMessage.setId(remoteMsgId);
|
||||||
errorMessage.setType(MessagePacket.TYPE_ERROR);
|
errorMessage.setType(im.conversations.android.xmpp.model.stanza.Message.Type.ERROR);
|
||||||
final Element error = errorMessage.addChild("error");
|
final Element error = errorMessage.addChild("error");
|
||||||
error.setAttribute("code", "404");
|
error.setAttribute("code", "404");
|
||||||
error.setAttribute("type", "cancel");
|
error.setAttribute("type", "cancel");
|
||||||
|
@ -722,7 +724,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
|
||||||
rtpSessionProposal.sessionId,
|
rtpSessionProposal.sessionId,
|
||||||
RtpEndUserState.RETRACTED);
|
RtpEndUserState.RETRACTED);
|
||||||
}
|
}
|
||||||
final MessagePacket messagePacket =
|
final var messagePacket =
|
||||||
mXmppConnectionService.getMessageGenerator().sessionRetract(rtpSessionProposal);
|
mXmppConnectionService.getMessageGenerator().sessionRetract(rtpSessionProposal);
|
||||||
writeLogMissedOutgoing(
|
writeLogMissedOutgoing(
|
||||||
account,
|
account,
|
||||||
|
@ -791,7 +793,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
|
||||||
this.rtpSessionProposals.put(proposal, DeviceDiscoveryState.SEARCHING);
|
this.rtpSessionProposals.put(proposal, DeviceDiscoveryState.SEARCHING);
|
||||||
mXmppConnectionService.notifyJingleRtpConnectionUpdate(
|
mXmppConnectionService.notifyJingleRtpConnectionUpdate(
|
||||||
account, proposal.with, proposal.sessionId, RtpEndUserState.FINDING_DEVICE);
|
account, proposal.with, proposal.sessionId, RtpEndUserState.FINDING_DEVICE);
|
||||||
final MessagePacket messagePacket =
|
final var messagePacket =
|
||||||
mXmppConnectionService.getMessageGenerator().sessionProposal(proposal);
|
mXmppConnectionService.getMessageGenerator().sessionProposal(proposal);
|
||||||
mXmppConnectionService.sendMessagePacket(account, messagePacket);
|
mXmppConnectionService.sendMessagePacket(account, messagePacket);
|
||||||
return proposal;
|
return proposal;
|
||||||
|
@ -801,7 +803,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
|
||||||
public void sendJingleMessageFinish(
|
public void sendJingleMessageFinish(
|
||||||
final Contact contact, final String sessionId, final Reason reason) {
|
final Contact contact, final String sessionId, final Reason reason) {
|
||||||
final var account = contact.getAccount();
|
final var account = contact.getAccount();
|
||||||
final MessagePacket messagePacket =
|
final var messagePacket =
|
||||||
mXmppConnectionService
|
mXmppConnectionService
|
||||||
.getMessageGenerator()
|
.getMessageGenerator()
|
||||||
.sessionFinish(contact.getJid(), sessionId, reason);
|
.sessionFinish(contact.getJid(), sessionId, reason);
|
||||||
|
@ -843,7 +845,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void deliverIbbPacket(final Account account, final IqPacket packet) {
|
public void deliverIbbPacket(final Account account, final Iq packet) {
|
||||||
final String sid;
|
final String sid;
|
||||||
final Element payload;
|
final Element payload;
|
||||||
final InbandBytestreamsTransport.PacketType packetType;
|
final InbandBytestreamsTransport.PacketType packetType;
|
||||||
|
@ -869,7 +871,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
|
||||||
Config.LOGTAG,
|
Config.LOGTAG,
|
||||||
account.getJid().asBareJid() + ": unable to deliver ibb packet. missing sid");
|
account.getJid().asBareJid() + ": unable to deliver ibb packet. missing sid");
|
||||||
account.getXmppConnection()
|
account.getXmppConnection()
|
||||||
.sendIqPacket(packet.generateResponse(IqPacket.TYPE.ERROR), null);
|
.sendIqPacket(packet.generateResponse(Iq.Type.ERROR), null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
for (final AbstractJingleConnection connection : this.connections.values()) {
|
for (final AbstractJingleConnection connection : this.connections.values()) {
|
||||||
|
@ -880,11 +882,11 @@ public class JingleConnectionManager extends AbstractConnectionManager {
|
||||||
if (inBandTransport.deliverPacket(packetType, packet.getFrom(), payload)) {
|
if (inBandTransport.deliverPacket(packetType, packet.getFrom(), payload)) {
|
||||||
account.getXmppConnection()
|
account.getXmppConnection()
|
||||||
.sendIqPacket(
|
.sendIqPacket(
|
||||||
packet.generateResponse(IqPacket.TYPE.RESULT), null);
|
packet.generateResponse(Iq.Type.RESULT), null);
|
||||||
} else {
|
} else {
|
||||||
account.getXmppConnection()
|
account.getXmppConnection()
|
||||||
.sendIqPacket(
|
.sendIqPacket(
|
||||||
packet.generateResponse(IqPacket.TYPE.ERROR), null);
|
packet.generateResponse(Iq.Type.ERROR), null);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -895,7 +897,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
|
||||||
Config.LOGTAG,
|
Config.LOGTAG,
|
||||||
account.getJid().asBareJid() + ": unable to deliver ibb packet with sid=" + sid);
|
account.getJid().asBareJid() + ": unable to deliver ibb packet with sid=" + sid);
|
||||||
account.getXmppConnection()
|
account.getXmppConnection()
|
||||||
.sendIqPacket(packet.generateResponse(IqPacket.TYPE.ERROR), null);
|
.sendIqPacket(packet.generateResponse(Iq.Type.ERROR), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void notifyRebound(final Account account) {
|
public void notifyRebound(final Account account) {
|
||||||
|
@ -946,7 +948,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
|
||||||
account.getJid().asBareJid()
|
account.getJid().asBareJid()
|
||||||
+ ": resending session proposal to "
|
+ ": resending session proposal to "
|
||||||
+ proposal.with);
|
+ proposal.with);
|
||||||
final MessagePacket messagePacket =
|
final var messagePacket =
|
||||||
mXmppConnectionService.getMessageGenerator().sessionProposal(proposal);
|
mXmppConnectionService.getMessageGenerator().sessionProposal(proposal);
|
||||||
mXmppConnectionService.sendMessagePacket(account, messagePacket);
|
mXmppConnectionService.sendMessagePacket(account, messagePacket);
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,13 +45,13 @@ import eu.siacs.conversations.xmpp.Jid;
|
||||||
import eu.siacs.conversations.xmpp.jingle.stanzas.Content;
|
import eu.siacs.conversations.xmpp.jingle.stanzas.Content;
|
||||||
import eu.siacs.conversations.xmpp.jingle.stanzas.Group;
|
import eu.siacs.conversations.xmpp.jingle.stanzas.Group;
|
||||||
import eu.siacs.conversations.xmpp.jingle.stanzas.IceUdpTransportInfo;
|
import eu.siacs.conversations.xmpp.jingle.stanzas.IceUdpTransportInfo;
|
||||||
import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket;
|
|
||||||
import eu.siacs.conversations.xmpp.jingle.stanzas.Proceed;
|
import eu.siacs.conversations.xmpp.jingle.stanzas.Proceed;
|
||||||
import eu.siacs.conversations.xmpp.jingle.stanzas.Propose;
|
import eu.siacs.conversations.xmpp.jingle.stanzas.Propose;
|
||||||
import eu.siacs.conversations.xmpp.jingle.stanzas.Reason;
|
import eu.siacs.conversations.xmpp.jingle.stanzas.Reason;
|
||||||
import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription;
|
import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription;
|
||||||
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
|
|
||||||
import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
|
import im.conversations.android.xmpp.model.jingle.Jingle;
|
||||||
|
import im.conversations.android.xmpp.model.stanza.Iq;
|
||||||
|
|
||||||
import org.webrtc.DtmfSender;
|
import org.webrtc.DtmfSender;
|
||||||
import org.webrtc.EglBase;
|
import org.webrtc.EglBase;
|
||||||
|
@ -145,24 +145,25 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
synchronized void deliverPacket(final JinglePacket jinglePacket) {
|
synchronized void deliverPacket(final Iq iq) {
|
||||||
switch (jinglePacket.getAction()) {
|
final var jingle = iq.getExtension(Jingle.class);
|
||||||
case SESSION_INITIATE -> receiveSessionInitiate(jinglePacket);
|
switch (jingle.getAction()) {
|
||||||
case TRANSPORT_INFO -> receiveTransportInfo(jinglePacket);
|
case SESSION_INITIATE -> receiveSessionInitiate(iq, jingle);
|
||||||
case SESSION_ACCEPT -> receiveSessionAccept(jinglePacket);
|
case TRANSPORT_INFO -> receiveTransportInfo(iq, jingle);
|
||||||
case SESSION_TERMINATE -> receiveSessionTerminate(jinglePacket);
|
case SESSION_ACCEPT -> receiveSessionAccept(iq, jingle);
|
||||||
case CONTENT_ADD -> receiveContentAdd(jinglePacket);
|
case SESSION_TERMINATE -> receiveSessionTerminate(iq);
|
||||||
case CONTENT_ACCEPT -> receiveContentAccept(jinglePacket);
|
case CONTENT_ADD -> receiveContentAdd(iq, jingle);
|
||||||
case CONTENT_REJECT -> receiveContentReject(jinglePacket);
|
case CONTENT_ACCEPT -> receiveContentAccept(iq);
|
||||||
case CONTENT_REMOVE -> receiveContentRemove(jinglePacket);
|
case CONTENT_REJECT -> receiveContentReject(iq, jingle);
|
||||||
case CONTENT_MODIFY -> receiveContentModify(jinglePacket);
|
case CONTENT_REMOVE -> receiveContentRemove(iq, jingle);
|
||||||
|
case CONTENT_MODIFY -> receiveContentModify(iq, jingle);
|
||||||
default -> {
|
default -> {
|
||||||
respondOk(jinglePacket);
|
respondOk(iq);
|
||||||
Log.d(
|
Log.d(
|
||||||
Config.LOGTAG,
|
Config.LOGTAG,
|
||||||
String.format(
|
String.format(
|
||||||
"%s: received unhandled jingle action %s",
|
"%s: received unhandled jingle action %s",
|
||||||
id.account.getJid().asBareJid(), jinglePacket.getAction()));
|
id.account.getJid().asBareJid(), jingle.getAction()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -193,9 +194,10 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
||||||
return webRTCWrapper.applyDtmfTone(tone);
|
return webRTCWrapper.applyDtmfTone(tone);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void receiveSessionTerminate(final JinglePacket jinglePacket) {
|
private void receiveSessionTerminate(final Iq jinglePacket) {
|
||||||
respondOk(jinglePacket);
|
respondOk(jinglePacket);
|
||||||
final JinglePacket.ReasonWrapper wrapper = jinglePacket.getReason();
|
final var jingle = jinglePacket.getExtension(Jingle.class);
|
||||||
|
final Jingle.ReasonWrapper wrapper = jingle.getReason();
|
||||||
final State previous = this.state;
|
final State previous = this.state;
|
||||||
Log.d(
|
Log.d(
|
||||||
Config.LOGTAG,
|
Config.LOGTAG,
|
||||||
|
@ -224,7 +226,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void receiveTransportInfo(final JinglePacket jinglePacket) {
|
private void receiveTransportInfo(final Iq jinglePacket, final Jingle jingle) {
|
||||||
// Due to the asynchronicity of processing session-init we might move from NULL|PROCEED to
|
// Due to the asynchronicity of processing session-init we might move from NULL|PROCEED to
|
||||||
// INITIALIZED only after transport-info has been received
|
// INITIALIZED only after transport-info has been received
|
||||||
if (isInState(
|
if (isInState(
|
||||||
|
@ -235,7 +237,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
||||||
State.SESSION_ACCEPTED)) {
|
State.SESSION_ACCEPTED)) {
|
||||||
final RtpContentMap contentMap;
|
final RtpContentMap contentMap;
|
||||||
try {
|
try {
|
||||||
contentMap = RtpContentMap.of(jinglePacket);
|
contentMap = RtpContentMap.of(jingle);
|
||||||
} catch (final IllegalArgumentException | NullPointerException e) {
|
} catch (final IllegalArgumentException | NullPointerException e) {
|
||||||
Log.d(
|
Log.d(
|
||||||
Config.LOGTAG,
|
Config.LOGTAG,
|
||||||
|
@ -265,7 +267,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
||||||
}
|
}
|
||||||
|
|
||||||
private void receiveTransportInfo(
|
private void receiveTransportInfo(
|
||||||
final JinglePacket jinglePacket, final RtpContentMap contentMap) {
|
final Iq jinglePacket, final RtpContentMap contentMap) {
|
||||||
final Set<Map.Entry<String, DescriptionTransport<RtpDescription, IceUdpTransportInfo>>>
|
final Set<Map.Entry<String, DescriptionTransport<RtpDescription, IceUdpTransportInfo>>>
|
||||||
candidates = contentMap.contents.entrySet();
|
candidates = contentMap.contents.entrySet();
|
||||||
final RtpContentMap remote = getRemoteContentMap();
|
final RtpContentMap remote = getRemoteContentMap();
|
||||||
|
@ -304,17 +306,17 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void receiveContentAdd(final JinglePacket jinglePacket) {
|
private void receiveContentAdd(final Iq iq, final Jingle jingle) {
|
||||||
final RtpContentMap modification;
|
final RtpContentMap modification;
|
||||||
try {
|
try {
|
||||||
modification = RtpContentMap.of(jinglePacket);
|
modification = RtpContentMap.of(jingle);
|
||||||
modification.requireContentDescriptions();
|
modification.requireContentDescriptions();
|
||||||
} catch (final RuntimeException e) {
|
} catch (final RuntimeException e) {
|
||||||
Log.d(
|
Log.d(
|
||||||
Config.LOGTAG,
|
Config.LOGTAG,
|
||||||
id.getAccount().getJid().asBareJid() + ": improperly formatted contents",
|
id.getAccount().getJid().asBareJid() + ": improperly formatted contents",
|
||||||
Throwables.getRootCause(e));
|
Throwables.getRootCause(e));
|
||||||
respondOk(jinglePacket);
|
respondOk(iq);
|
||||||
webRTCWrapper.close();
|
webRTCWrapper.close();
|
||||||
sendSessionTerminate(Reason.of(e), e.getMessage());
|
sendSessionTerminate(Reason.of(e), e.getMessage());
|
||||||
return;
|
return;
|
||||||
|
@ -330,12 +332,12 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
||||||
new FutureCallback<>() {
|
new FutureCallback<>() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess(final RtpContentMap rtpContentMap) {
|
public void onSuccess(final RtpContentMap rtpContentMap) {
|
||||||
receiveContentAdd(jinglePacket, rtpContentMap);
|
receiveContentAdd(iq, rtpContentMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFailure(@NonNull Throwable throwable) {
|
public void onFailure(@NonNull Throwable throwable) {
|
||||||
respondOk(jinglePacket);
|
respondOk(iq);
|
||||||
final Throwable rootCause = Throwables.getRootCause(throwable);
|
final Throwable rootCause = Throwables.getRootCause(throwable);
|
||||||
Log.d(
|
Log.d(
|
||||||
Config.LOGTAG,
|
Config.LOGTAG,
|
||||||
|
@ -349,12 +351,12 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
||||||
},
|
},
|
||||||
MoreExecutors.directExecutor());
|
MoreExecutors.directExecutor());
|
||||||
} else {
|
} else {
|
||||||
terminateWithOutOfOrder(jinglePacket);
|
terminateWithOutOfOrder(iq);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void receiveContentAdd(
|
private void receiveContentAdd(
|
||||||
final JinglePacket jinglePacket, final RtpContentMap modification) {
|
final Iq jinglePacket, final RtpContentMap modification) {
|
||||||
final RtpContentMap remote = getRemoteContentMap();
|
final RtpContentMap remote = getRemoteContentMap();
|
||||||
if (!Collections.disjoint(modification.getNames(), remote.getNames())) {
|
if (!Collections.disjoint(modification.getNames(), remote.getNames())) {
|
||||||
respondOk(jinglePacket);
|
respondOk(jinglePacket);
|
||||||
|
@ -406,10 +408,11 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void receiveContentAccept(final JinglePacket jinglePacket) {
|
private void receiveContentAccept(final Iq jinglePacket) {
|
||||||
|
final var jingle = jinglePacket.getExtension(Jingle.class);
|
||||||
final RtpContentMap receivedContentAccept;
|
final RtpContentMap receivedContentAccept;
|
||||||
try {
|
try {
|
||||||
receivedContentAccept = RtpContentMap.of(jinglePacket);
|
receivedContentAccept = RtpContentMap.of(jingle);
|
||||||
receivedContentAccept.requireContentDescriptions();
|
receivedContentAccept.requireContentDescriptions();
|
||||||
} catch (final RuntimeException e) {
|
} catch (final RuntimeException e) {
|
||||||
Log.d(
|
Log.d(
|
||||||
|
@ -494,14 +497,14 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
||||||
updateEndUserState();
|
updateEndUserState();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void receiveContentModify(final JinglePacket jinglePacket) {
|
private void receiveContentModify(final Iq jinglePacket, final Jingle jingle) {
|
||||||
if (this.state != State.SESSION_ACCEPTED) {
|
if (this.state != State.SESSION_ACCEPTED) {
|
||||||
terminateWithOutOfOrder(jinglePacket);
|
terminateWithOutOfOrder(jinglePacket);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final Map<String, Content.Senders> modification =
|
final Map<String, Content.Senders> modification =
|
||||||
Maps.transformEntries(
|
Maps.transformEntries(
|
||||||
jinglePacket.getJingleContents(), (key, value) -> value.getSenders());
|
jingle.getJingleContents(), (key, value) -> value.getSenders());
|
||||||
final boolean isInitiator = isInitiator();
|
final boolean isInitiator = isInitiator();
|
||||||
final RtpContentMap currentOutgoing = this.outgoingContentAdd;
|
final RtpContentMap currentOutgoing = this.outgoingContentAdd;
|
||||||
final RtpContentMap remoteContentMap = this.getRemoteContentMap();
|
final RtpContentMap remoteContentMap = this.getRemoteContentMap();
|
||||||
|
@ -604,10 +607,10 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
||||||
return candidateBuilder.build();
|
return candidateBuilder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void receiveContentReject(final JinglePacket jinglePacket) {
|
private void receiveContentReject(final Iq jinglePacket, final Jingle jingle) {
|
||||||
final RtpContentMap receivedContentReject;
|
final RtpContentMap receivedContentReject;
|
||||||
try {
|
try {
|
||||||
receivedContentReject = RtpContentMap.of(jinglePacket);
|
receivedContentReject = RtpContentMap.of(jingle);
|
||||||
} catch (final RuntimeException e) {
|
} catch (final RuntimeException e) {
|
||||||
Log.d(
|
Log.d(
|
||||||
Config.LOGTAG,
|
Config.LOGTAG,
|
||||||
|
@ -660,10 +663,10 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
||||||
+ summary);
|
+ summary);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void receiveContentRemove(final JinglePacket jinglePacket) {
|
private void receiveContentRemove(final Iq jinglePacket, final Jingle jingle) {
|
||||||
final RtpContentMap receivedContentRemove;
|
final RtpContentMap receivedContentRemove;
|
||||||
try {
|
try {
|
||||||
receivedContentRemove = RtpContentMap.of(jinglePacket);
|
receivedContentRemove = RtpContentMap.of(jingle);
|
||||||
receivedContentRemove.requireContentDescriptions();
|
receivedContentRemove.requireContentDescriptions();
|
||||||
} catch (final RuntimeException e) {
|
} catch (final RuntimeException e) {
|
||||||
Log.d(
|
Log.d(
|
||||||
|
@ -697,8 +700,8 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
||||||
String.format(
|
String.format(
|
||||||
"%s only supports %s as a means to retract a not yet accepted %s",
|
"%s only supports %s as a means to retract a not yet accepted %s",
|
||||||
BuildConfig.APP_NAME,
|
BuildConfig.APP_NAME,
|
||||||
JinglePacket.Action.CONTENT_REMOVE,
|
Jingle.Action.CONTENT_REMOVE,
|
||||||
JinglePacket.Action.CONTENT_ADD));
|
Jingle.Action.CONTENT_ADD));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -723,10 +726,10 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.outgoingContentAdd = null;
|
this.outgoingContentAdd = null;
|
||||||
final JinglePacket retract =
|
final Iq retract =
|
||||||
outgoingContentAdd
|
outgoingContentAdd
|
||||||
.toStub()
|
.toStub()
|
||||||
.toJinglePacket(JinglePacket.Action.CONTENT_REMOVE, id.sessionId);
|
.toJinglePacket(Jingle.Action.CONTENT_REMOVE, id.sessionId);
|
||||||
this.send(retract);
|
this.send(retract);
|
||||||
Log.d(
|
Log.d(
|
||||||
Config.LOGTAG,
|
Config.LOGTAG,
|
||||||
|
@ -782,16 +785,16 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
||||||
"content addition is receive only. we want to upgrade to 'both'");
|
"content addition is receive only. we want to upgrade to 'both'");
|
||||||
final RtpContentMap modifiedSenders =
|
final RtpContentMap modifiedSenders =
|
||||||
incomingContentAdd.modifiedSenders(Content.Senders.BOTH);
|
incomingContentAdd.modifiedSenders(Content.Senders.BOTH);
|
||||||
final JinglePacket proposedContentModification =
|
final Iq proposedContentModification =
|
||||||
modifiedSenders
|
modifiedSenders
|
||||||
.toStub()
|
.toStub()
|
||||||
.toJinglePacket(JinglePacket.Action.CONTENT_MODIFY, id.sessionId);
|
.toJinglePacket(Jingle.Action.CONTENT_MODIFY, id.sessionId);
|
||||||
proposedContentModification.setTo(id.with);
|
proposedContentModification.setTo(id.with);
|
||||||
xmppConnectionService.sendIqPacket(
|
xmppConnectionService.sendIqPacket(
|
||||||
id.account,
|
id.account,
|
||||||
proposedContentModification,
|
proposedContentModification,
|
||||||
(account, response) -> {
|
(response) -> {
|
||||||
if (response.getType() == IqPacket.TYPE.RESULT) {
|
if (response.getType() == Iq.Type.RESULT) {
|
||||||
Log.d(
|
Log.d(
|
||||||
Config.LOGTAG,
|
Config.LOGTAG,
|
||||||
id.account.getJid().asBareJid()
|
id.account.getJid().asBareJid()
|
||||||
|
@ -885,7 +888,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFailure(@NonNull final Throwable throwable) {
|
public void onFailure(@NonNull final Throwable throwable) {
|
||||||
failureToPerformAction(JinglePacket.Action.CONTENT_ACCEPT, throwable);
|
failureToPerformAction(Jingle.Action.CONTENT_ACCEPT, throwable);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
MoreExecutors.directExecutor());
|
MoreExecutors.directExecutor());
|
||||||
|
@ -897,9 +900,9 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendContentAccept(final RtpContentMap contentAcceptMap) {
|
private void sendContentAccept(final RtpContentMap contentAcceptMap) {
|
||||||
final JinglePacket jinglePacket =
|
final Iq iq =
|
||||||
contentAcceptMap.toJinglePacket(JinglePacket.Action.CONTENT_ACCEPT, id.sessionId);
|
contentAcceptMap.toJinglePacket(Jingle.Action.CONTENT_ACCEPT, id.sessionId);
|
||||||
send(jinglePacket);
|
send(iq);
|
||||||
}
|
}
|
||||||
|
|
||||||
public synchronized void rejectContentAdd() {
|
public synchronized void rejectContentAdd() {
|
||||||
|
@ -913,20 +916,20 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
||||||
}
|
}
|
||||||
|
|
||||||
private void rejectContentAdd(final RtpContentMap contentMap) {
|
private void rejectContentAdd(final RtpContentMap contentMap) {
|
||||||
final JinglePacket jinglePacket =
|
final Iq iq =
|
||||||
contentMap
|
contentMap
|
||||||
.toStub()
|
.toStub()
|
||||||
.toJinglePacket(JinglePacket.Action.CONTENT_REJECT, id.sessionId);
|
.toJinglePacket(Jingle.Action.CONTENT_REJECT, id.sessionId);
|
||||||
Log.d(
|
Log.d(
|
||||||
Config.LOGTAG,
|
Config.LOGTAG,
|
||||||
id.getAccount().getJid().asBareJid()
|
id.getAccount().getJid().asBareJid()
|
||||||
+ ": rejecting content "
|
+ ": rejecting content "
|
||||||
+ ContentAddition.summary(contentMap));
|
+ ContentAddition.summary(contentMap));
|
||||||
send(jinglePacket);
|
send(iq);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean checkForIceRestart(
|
private boolean checkForIceRestart(
|
||||||
final JinglePacket jinglePacket, final RtpContentMap rtpContentMap) {
|
final Iq jinglePacket, final RtpContentMap rtpContentMap) {
|
||||||
final RtpContentMap existing = getRemoteContentMap();
|
final RtpContentMap existing = getRemoteContentMap();
|
||||||
final Set<IceUdpTransportInfo.Credentials> existingCredentials;
|
final Set<IceUdpTransportInfo.Credentials> existingCredentials;
|
||||||
final IceUdpTransportInfo.Credentials newCredentials;
|
final IceUdpTransportInfo.Credentials newCredentials;
|
||||||
|
@ -1005,7 +1008,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean applyIceRestart(
|
private boolean applyIceRestart(
|
||||||
final JinglePacket jinglePacket,
|
final Iq jinglePacket,
|
||||||
final RtpContentMap restartContentMap,
|
final RtpContentMap restartContentMap,
|
||||||
final boolean isOffer)
|
final boolean isOffer)
|
||||||
throws ExecutionException, InterruptedException {
|
throws ExecutionException, InterruptedException {
|
||||||
|
@ -1106,7 +1109,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
||||||
}
|
}
|
||||||
|
|
||||||
private ListenableFuture<RtpContentMap> receiveRtpContentMap(
|
private ListenableFuture<RtpContentMap> receiveRtpContentMap(
|
||||||
final JinglePacket jinglePacket, final boolean expectVerification) {
|
final Jingle jinglePacket, final boolean expectVerification) {
|
||||||
try {
|
try {
|
||||||
return receiveRtpContentMap(RtpContentMap.of(jinglePacket), expectVerification);
|
return receiveRtpContentMap(RtpContentMap.of(jinglePacket), expectVerification);
|
||||||
} catch (final Exception e) {
|
} catch (final Exception e) {
|
||||||
|
@ -1149,12 +1152,12 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void receiveSessionInitiate(final JinglePacket jinglePacket) {
|
private void receiveSessionInitiate(final Iq jinglePacket, final Jingle jingle) {
|
||||||
if (isInitiator()) {
|
if (isInitiator()) {
|
||||||
receiveOutOfOrderAction(jinglePacket, JinglePacket.Action.SESSION_INITIATE);
|
receiveOutOfOrderAction(jinglePacket, Jingle.Action.SESSION_INITIATE);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final ListenableFuture<RtpContentMap> future = receiveRtpContentMap(jinglePacket, false);
|
final ListenableFuture<RtpContentMap> future = receiveRtpContentMap(jingle, false);
|
||||||
Futures.addCallback(
|
Futures.addCallback(
|
||||||
future,
|
future,
|
||||||
new FutureCallback<>() {
|
new FutureCallback<>() {
|
||||||
|
@ -1173,7 +1176,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
||||||
}
|
}
|
||||||
|
|
||||||
private void receiveSessionInitiate(
|
private void receiveSessionInitiate(
|
||||||
final JinglePacket jinglePacket, final RtpContentMap contentMap) {
|
final Iq jinglePacket, final RtpContentMap contentMap) {
|
||||||
try {
|
try {
|
||||||
contentMap.requireContentDescriptions();
|
contentMap.requireContentDescriptions();
|
||||||
contentMap.requireDTLSFingerprint(true);
|
contentMap.requireDTLSFingerprint(true);
|
||||||
|
@ -1233,13 +1236,13 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void receiveSessionAccept(final JinglePacket jinglePacket) {
|
private void receiveSessionAccept(final Iq jinglePacket, final Jingle jingle) {
|
||||||
if (isResponder()) {
|
if (isResponder()) {
|
||||||
receiveOutOfOrderAction(jinglePacket, JinglePacket.Action.SESSION_ACCEPT);
|
receiveOutOfOrderAction(jinglePacket, Jingle.Action.SESSION_ACCEPT);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final ListenableFuture<RtpContentMap> future =
|
final ListenableFuture<RtpContentMap> future =
|
||||||
receiveRtpContentMap(jinglePacket, this.omemoVerification.hasFingerprint());
|
receiveRtpContentMap(jingle, this.omemoVerification.hasFingerprint());
|
||||||
Futures.addCallback(
|
Futures.addCallback(
|
||||||
future,
|
future,
|
||||||
new FutureCallback<>() {
|
new FutureCallback<>() {
|
||||||
|
@ -1264,7 +1267,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
||||||
}
|
}
|
||||||
|
|
||||||
private void receiveSessionAccept(
|
private void receiveSessionAccept(
|
||||||
final JinglePacket jinglePacket, final RtpContentMap contentMap) {
|
final Iq jinglePacket, final RtpContentMap contentMap) {
|
||||||
try {
|
try {
|
||||||
contentMap.requireContentDescriptions();
|
contentMap.requireContentDescriptions();
|
||||||
contentMap.requireDTLSFingerprint();
|
contentMap.requireDTLSFingerprint();
|
||||||
|
@ -1409,7 +1412,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
||||||
}
|
}
|
||||||
|
|
||||||
private void failureToPerformAction(
|
private void failureToPerformAction(
|
||||||
final JinglePacket.Action action, final Throwable throwable) {
|
final Jingle.Action action, final Throwable throwable) {
|
||||||
if (isTerminated()) {
|
if (isTerminated()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1480,8 +1483,8 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
transitionOrThrow(State.SESSION_ACCEPTED);
|
transitionOrThrow(State.SESSION_ACCEPTED);
|
||||||
final JinglePacket sessionAccept =
|
final Iq sessionAccept =
|
||||||
rtpContentMap.toJinglePacket(JinglePacket.Action.SESSION_ACCEPT, id.sessionId);
|
rtpContentMap.toJinglePacket(Jingle.Action.SESSION_ACCEPT, id.sessionId);
|
||||||
send(sessionAccept);
|
send(sessionAccept);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1951,8 +1954,8 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.transitionOrThrow(targetState);
|
this.transitionOrThrow(targetState);
|
||||||
final JinglePacket sessionInitiate =
|
final Iq sessionInitiate =
|
||||||
rtpContentMap.toJinglePacket(JinglePacket.Action.SESSION_INITIATE, id.sessionId);
|
rtpContentMap.toJinglePacket(Jingle.Action.SESSION_INITIATE, id.sessionId);
|
||||||
send(sessionInitiate);
|
send(sessionInitiate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2020,9 +2023,9 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
||||||
+ contentName);
|
+ contentName);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final JinglePacket jinglePacket =
|
final Iq iq =
|
||||||
transportInfo.toJinglePacket(JinglePacket.Action.TRANSPORT_INFO, id.sessionId);
|
transportInfo.toJinglePacket(Jingle.Action.TRANSPORT_INFO, id.sessionId);
|
||||||
send(jinglePacket);
|
send(iq);
|
||||||
}
|
}
|
||||||
|
|
||||||
public RtpEndUserState getEndUserState() {
|
public RtpEndUserState getEndUserState() {
|
||||||
|
@ -2340,7 +2343,8 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
||||||
this.jingleConnectionManager.ensureConnectionIsRegistered(this);
|
this.jingleConnectionManager.ensureConnectionIsRegistered(this);
|
||||||
this.webRTCWrapper.setup(this.xmppConnectionService);
|
this.webRTCWrapper.setup(this.xmppConnectionService);
|
||||||
this.webRTCWrapper.initializePeerConnection(media, iceServers, trickle);
|
this.webRTCWrapper.initializePeerConnection(media, iceServers, trickle);
|
||||||
this.webRTCWrapper.setMicrophoneEnabledOrThrow(callIntegration.isMicrophoneEnabled());
|
// this.webRTCWrapper.setMicrophoneEnabledOrThrow(callIntegration.isMicrophoneEnabled());
|
||||||
|
this.webRTCWrapper.setMicrophoneEnabledOrThrow(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void acceptCallFromProposed() {
|
private void acceptCallFromProposed() {
|
||||||
|
@ -2375,8 +2379,8 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendJingleMessage(final String action, final Jid to) {
|
private void sendJingleMessage(final String action, final Jid to) {
|
||||||
final MessagePacket messagePacket = new MessagePacket();
|
final var messagePacket = new im.conversations.android.xmpp.model.stanza.Message();
|
||||||
messagePacket.setType(MessagePacket.TYPE_CHAT); // we want to carbon copy those
|
messagePacket.setType(im.conversations.android.xmpp.model.stanza.Message.Type.CHAT); // we want to carbon copy those
|
||||||
messagePacket.setTo(to);
|
messagePacket.setTo(to);
|
||||||
final Element intent =
|
final Element intent =
|
||||||
messagePacket
|
messagePacket
|
||||||
|
@ -2397,7 +2401,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
||||||
|
|
||||||
private void sendJingleMessageFinish(final Reason reason) {
|
private void sendJingleMessageFinish(final Reason reason) {
|
||||||
final var account = id.getAccount();
|
final var account = id.getAccount();
|
||||||
final MessagePacket messagePacket =
|
final var messagePacket =
|
||||||
xmppConnectionService
|
xmppConnectionService
|
||||||
.getMessageGenerator()
|
.getMessageGenerator()
|
||||||
.sessionFinish(id.with, id.sessionId, reason);
|
.sessionFinish(id.with, id.sessionId, reason);
|
||||||
|
@ -2556,34 +2560,34 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
||||||
|
|
||||||
private void initiateIceRestart(final RtpContentMap rtpContentMap) {
|
private void initiateIceRestart(final RtpContentMap rtpContentMap) {
|
||||||
final RtpContentMap transportInfo = rtpContentMap.transportInfo();
|
final RtpContentMap transportInfo = rtpContentMap.transportInfo();
|
||||||
final JinglePacket jinglePacket =
|
final Iq iq =
|
||||||
transportInfo.toJinglePacket(JinglePacket.Action.TRANSPORT_INFO, id.sessionId);
|
transportInfo.toJinglePacket(Jingle.Action.TRANSPORT_INFO, id.sessionId);
|
||||||
Log.d(Config.LOGTAG, "initiating ice restart: " + jinglePacket);
|
Log.d(Config.LOGTAG, "initiating ice restart: " + iq);
|
||||||
jinglePacket.setTo(id.with);
|
iq.setTo(id.with);
|
||||||
xmppConnectionService.sendIqPacket(
|
xmppConnectionService.sendIqPacket(
|
||||||
id.account,
|
id.account,
|
||||||
jinglePacket,
|
iq,
|
||||||
(account, response) -> {
|
(response) -> {
|
||||||
if (response.getType() == IqPacket.TYPE.RESULT) {
|
if (response.getType() == Iq.Type.RESULT) {
|
||||||
Log.d(Config.LOGTAG, "received success to our ice restart");
|
Log.d(Config.LOGTAG, "received success to our ice restart");
|
||||||
setLocalContentMap(rtpContentMap);
|
setLocalContentMap(rtpContentMap);
|
||||||
webRTCWrapper.setIsReadyToReceiveIceCandidates(true);
|
webRTCWrapper.setIsReadyToReceiveIceCandidates(true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (response.getType() == IqPacket.TYPE.ERROR) {
|
if (response.getType() == Iq.Type.ERROR) {
|
||||||
if (isTieBreak(response)) {
|
if (isTieBreak(response)) {
|
||||||
Log.d(Config.LOGTAG, "received tie-break as result of ice restart");
|
Log.d(Config.LOGTAG, "received tie-break as result of ice restart");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
handleIqErrorResponse(response);
|
handleIqErrorResponse(response);
|
||||||
}
|
}
|
||||||
if (response.getType() == IqPacket.TYPE.TIMEOUT) {
|
if (response.getType() == Iq.Type.TIMEOUT) {
|
||||||
handleIqTimeoutResponse(response);
|
handleIqTimeoutResponse(response);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isTieBreak(final IqPacket response) {
|
private boolean isTieBreak(final Iq response) {
|
||||||
final Element error = response.findChild("error");
|
final Element error = response.findChild("error");
|
||||||
return error != null && error.hasChild("tie-break", Namespace.JINGLE_ERRORS);
|
return error != null && error.hasChild("tie-break", Namespace.JINGLE_ERRORS);
|
||||||
}
|
}
|
||||||
|
@ -2604,7 +2608,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onFailure(@NonNull Throwable throwable) {
|
public void onFailure(@NonNull Throwable throwable) {
|
||||||
failureToPerformAction(JinglePacket.Action.CONTENT_ADD, throwable);
|
failureToPerformAction(Jingle.Action.CONTENT_ADD, throwable);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
MoreExecutors.directExecutor());
|
MoreExecutors.directExecutor());
|
||||||
|
@ -2612,21 +2616,21 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
||||||
|
|
||||||
private void sendContentAdd(final RtpContentMap contentAdd) {
|
private void sendContentAdd(final RtpContentMap contentAdd) {
|
||||||
|
|
||||||
final JinglePacket jinglePacket =
|
final Iq iq =
|
||||||
contentAdd.toJinglePacket(JinglePacket.Action.CONTENT_ADD, id.sessionId);
|
contentAdd.toJinglePacket(Jingle.Action.CONTENT_ADD, id.sessionId);
|
||||||
jinglePacket.setTo(id.with);
|
iq.setTo(id.with);
|
||||||
xmppConnectionService.sendIqPacket(
|
xmppConnectionService.sendIqPacket(
|
||||||
id.account,
|
id.account,
|
||||||
jinglePacket,
|
iq,
|
||||||
(connection, response) -> {
|
(response) -> {
|
||||||
if (response.getType() == IqPacket.TYPE.RESULT) {
|
if (response.getType() == Iq.Type.RESULT) {
|
||||||
Log.d(
|
Log.d(
|
||||||
Config.LOGTAG,
|
Config.LOGTAG,
|
||||||
id.getAccount().getJid().asBareJid()
|
id.getAccount().getJid().asBareJid()
|
||||||
+ ": received ACK to our content-add");
|
+ ": received ACK to our content-add");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (response.getType() == IqPacket.TYPE.ERROR) {
|
if (response.getType() == Iq.Type.ERROR) {
|
||||||
if (isTieBreak(response)) {
|
if (isTieBreak(response)) {
|
||||||
this.outgoingContentAdd = null;
|
this.outgoingContentAdd = null;
|
||||||
Log.d(Config.LOGTAG, "received tie-break as result of our content-add");
|
Log.d(Config.LOGTAG, "received tie-break as result of our content-add");
|
||||||
|
@ -2634,7 +2638,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
||||||
}
|
}
|
||||||
handleIqErrorResponse(response);
|
handleIqErrorResponse(response);
|
||||||
}
|
}
|
||||||
if (response.getType() == IqPacket.TYPE.TIMEOUT) {
|
if (response.getType() == Iq.Type.TIMEOUT) {
|
||||||
handleIqTimeoutResponse(response);
|
handleIqTimeoutResponse(response);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -2782,7 +2786,12 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCallIntegrationMicrophoneEnabled(final boolean enabled) {
|
public void onCallIntegrationMicrophoneEnabled(final boolean enabled) {
|
||||||
this.webRTCWrapper.setMicrophoneEnabled(enabled);
|
// this is called every time we switch audio devices. Thus it would re-enable a microphone
|
||||||
|
// that was previous disabled by the user. A proper implementation would probably be to
|
||||||
|
// track user choice and enable the microphone with a userEnabled() &&
|
||||||
|
// callIntegration.isMicrophoneEnabled() condition
|
||||||
|
Log.d(Config.LOGTAG, "ignoring onCallIntegrationMicrophoneEnabled(" + enabled + ")");
|
||||||
|
// this.webRTCWrapper.setMicrophoneEnabled(enabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -2827,13 +2836,13 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
||||||
|
|
||||||
private void discoverIceServers(final OnIceServersDiscovered onIceServersDiscovered) {
|
private void discoverIceServers(final OnIceServersDiscovered onIceServersDiscovered) {
|
||||||
if (id.account.getXmppConnection().getFeatures().externalServiceDiscovery()) {
|
if (id.account.getXmppConnection().getFeatures().externalServiceDiscovery()) {
|
||||||
final IqPacket request = new IqPacket(IqPacket.TYPE.GET);
|
final Iq request = new Iq(Iq.Type.GET);
|
||||||
request.setTo(id.account.getDomain());
|
request.setTo(id.account.getDomain());
|
||||||
request.addChild("services", Namespace.EXTERNAL_SERVICE_DISCOVERY);
|
request.addChild("services", Namespace.EXTERNAL_SERVICE_DISCOVERY);
|
||||||
xmppConnectionService.sendIqPacket(
|
xmppConnectionService.sendIqPacket(
|
||||||
id.account,
|
id.account,
|
||||||
request,
|
request,
|
||||||
(account, response) -> {
|
(response) -> {
|
||||||
final var iceServers = IceServers.parse(response);
|
final var iceServers = IceServers.parse(response);
|
||||||
if (iceServers.isEmpty()) {
|
if (iceServers.isEmpty()) {
|
||||||
Log.w(
|
Log.w(
|
||||||
|
|
|
@ -18,9 +18,9 @@ import eu.siacs.conversations.xmpp.jingle.stanzas.GenericDescription;
|
||||||
import eu.siacs.conversations.xmpp.jingle.stanzas.GenericTransportInfo;
|
import eu.siacs.conversations.xmpp.jingle.stanzas.GenericTransportInfo;
|
||||||
import eu.siacs.conversations.xmpp.jingle.stanzas.Group;
|
import eu.siacs.conversations.xmpp.jingle.stanzas.Group;
|
||||||
import eu.siacs.conversations.xmpp.jingle.stanzas.IceUdpTransportInfo;
|
import eu.siacs.conversations.xmpp.jingle.stanzas.IceUdpTransportInfo;
|
||||||
import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket;
|
|
||||||
import eu.siacs.conversations.xmpp.jingle.stanzas.OmemoVerifiedIceUdpTransportInfo;
|
import eu.siacs.conversations.xmpp.jingle.stanzas.OmemoVerifiedIceUdpTransportInfo;
|
||||||
import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription;
|
import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription;
|
||||||
|
import im.conversations.android.xmpp.model.jingle.Jingle;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
@ -39,7 +39,7 @@ public class RtpContentMap extends AbstractContentMap<RtpDescription, IceUdpTran
|
||||||
super(group, contents);
|
super(group, contents);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static RtpContentMap of(final JinglePacket jinglePacket) {
|
public static RtpContentMap of(final Jingle jinglePacket) {
|
||||||
final Map<String, DescriptionTransport<RtpDescription, IceUdpTransportInfo>> contents =
|
final Map<String, DescriptionTransport<RtpDescription, IceUdpTransportInfo>> contents =
|
||||||
of(jinglePacket.getJingleContents());
|
of(jinglePacket.getJingleContents());
|
||||||
if (isOmemoVerified(contents)) {
|
if (isOmemoVerified(contents)) {
|
||||||
|
@ -53,7 +53,7 @@ public class RtpContentMap extends AbstractContentMap<RtpDescription, IceUdpTran
|
||||||
Map<String, DescriptionTransport<RtpDescription, IceUdpTransportInfo>> contents) {
|
Map<String, DescriptionTransport<RtpDescription, IceUdpTransportInfo>> contents) {
|
||||||
final Collection<DescriptionTransport<RtpDescription, IceUdpTransportInfo>> values =
|
final Collection<DescriptionTransport<RtpDescription, IceUdpTransportInfo>> values =
|
||||||
contents.values();
|
contents.values();
|
||||||
if (values.size() == 0) {
|
if (values.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
for (final DescriptionTransport<RtpDescription, IceUdpTransportInfo> descriptionTransport :
|
for (final DescriptionTransport<RtpDescription, IceUdpTransportInfo> descriptionTransport :
|
||||||
|
|
34
src/main/java/im/conversations/android/xmpp/Entity.java
Normal file
34
src/main/java/im/conversations/android/xmpp/Entity.java
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
package im.conversations.android.xmpp;
|
||||||
|
|
||||||
|
import org.jxmpp.jid.Jid;
|
||||||
|
|
||||||
|
public abstract class Entity {
|
||||||
|
|
||||||
|
public final Jid address;
|
||||||
|
|
||||||
|
private Entity(final Jid address) {
|
||||||
|
this.address = address;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class DiscoItem extends Entity {
|
||||||
|
|
||||||
|
private DiscoItem(Jid address) {
|
||||||
|
super(address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Presence extends Entity {
|
||||||
|
|
||||||
|
private Presence(Jid address) {
|
||||||
|
super(address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Presence presence(final Jid address) {
|
||||||
|
return new Presence(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static DiscoItem discoItem(final Jid address) {
|
||||||
|
return new DiscoItem(address);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,133 @@
|
||||||
|
package im.conversations.android.xmpp;
|
||||||
|
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
import com.google.common.collect.Collections2;
|
||||||
|
import com.google.common.collect.ComparisonChain;
|
||||||
|
import com.google.common.collect.Ordering;
|
||||||
|
import com.google.common.hash.Hashing;
|
||||||
|
import com.google.common.io.BaseEncoding;
|
||||||
|
import im.conversations.android.xmpp.model.data.Data;
|
||||||
|
import im.conversations.android.xmpp.model.data.Field;
|
||||||
|
import im.conversations.android.xmpp.model.disco.info.Feature;
|
||||||
|
import im.conversations.android.xmpp.model.disco.info.Identity;
|
||||||
|
import im.conversations.android.xmpp.model.disco.info.InfoQuery;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public final class EntityCapabilities {
|
||||||
|
public static EntityCapsHash hash(final InfoQuery info) {
|
||||||
|
final StringBuilder s = new StringBuilder();
|
||||||
|
final List<Identity> orderedIdentities =
|
||||||
|
Ordering.from(
|
||||||
|
(Comparator<Identity>)
|
||||||
|
(a, b) ->
|
||||||
|
ComparisonChain.start()
|
||||||
|
.compare(
|
||||||
|
blankNull(a.getCategory()),
|
||||||
|
blankNull(b.getCategory()))
|
||||||
|
.compare(
|
||||||
|
blankNull(a.getType()),
|
||||||
|
blankNull(b.getType()))
|
||||||
|
.compare(
|
||||||
|
blankNull(a.getLang()),
|
||||||
|
blankNull(b.getLang()))
|
||||||
|
.compare(
|
||||||
|
blankNull(a.getIdentityName()),
|
||||||
|
blankNull(b.getIdentityName()))
|
||||||
|
.result())
|
||||||
|
.sortedCopy(info.getIdentities());
|
||||||
|
|
||||||
|
for (final Identity id : orderedIdentities) {
|
||||||
|
s.append(blankNull(id.getCategory()))
|
||||||
|
.append("/")
|
||||||
|
.append(blankNull(id.getType()))
|
||||||
|
.append("/")
|
||||||
|
.append(blankNull(id.getLang()))
|
||||||
|
.append("/")
|
||||||
|
.append(blankNull(id.getIdentityName()))
|
||||||
|
.append("<");
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<String> features =
|
||||||
|
Ordering.natural()
|
||||||
|
.sortedCopy(Collections2.transform(info.getFeatures(), Feature::getVar));
|
||||||
|
for (final String feature : features) {
|
||||||
|
s.append(clean(feature)).append("<");
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<Data> extensions =
|
||||||
|
Ordering.from(Comparator.comparing(Data::getFormType))
|
||||||
|
.sortedCopy(info.getExtensions(Data.class));
|
||||||
|
|
||||||
|
for (final Data extension : extensions) {
|
||||||
|
s.append(clean(extension.getFormType())).append("<");
|
||||||
|
final List<Field> fields =
|
||||||
|
Ordering.from(
|
||||||
|
Comparator.comparing(
|
||||||
|
(Field lhs) -> Strings.nullToEmpty(lhs.getFieldName())))
|
||||||
|
.sortedCopy(extension.getFields());
|
||||||
|
for (final Field field : fields) {
|
||||||
|
s.append(Strings.nullToEmpty(field.getFieldName())).append("<");
|
||||||
|
final List<String> values = Ordering.natural().sortedCopy(field.getValues());
|
||||||
|
for (final String value : values) {
|
||||||
|
s.append(blankNull(value)).append("<");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new EntityCapsHash(
|
||||||
|
Hashing.sha1().hashString(s.toString(), StandardCharsets.UTF_8).asBytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String clean(String s) {
|
||||||
|
return s.replace("<", "<");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String blankNull(String s) {
|
||||||
|
return s == null ? "" : clean(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract static class Hash {
|
||||||
|
public final byte[] hash;
|
||||||
|
|
||||||
|
protected Hash(byte[] hash) {
|
||||||
|
this.hash = hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String encoded() {
|
||||||
|
return BaseEncoding.base64().encode(hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract String capabilityNode(final String node);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
Hash hash1 = (Hash) o;
|
||||||
|
return Arrays.equals(hash, hash1.hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Arrays.hashCode(hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class EntityCapsHash extends Hash {
|
||||||
|
|
||||||
|
protected EntityCapsHash(byte[] hash) {
|
||||||
|
super(hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String capabilityNode(String node) {
|
||||||
|
return String.format("%s#%s", node, encoded());
|
||||||
|
}
|
||||||
|
|
||||||
|
public static EntityCapsHash of(final String encoded) {
|
||||||
|
return new EntityCapsHash(BaseEncoding.base64().decode(encoded));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,185 @@
|
||||||
|
package im.conversations.android.xmpp;
|
||||||
|
|
||||||
|
import com.google.common.base.Joiner;
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
import com.google.common.collect.Collections2;
|
||||||
|
import com.google.common.collect.Ordering;
|
||||||
|
import com.google.common.hash.HashFunction;
|
||||||
|
import com.google.common.hash.Hashing;
|
||||||
|
import com.google.common.io.BaseEncoding;
|
||||||
|
import com.google.common.primitives.Bytes;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.xml.Namespace;
|
||||||
|
import im.conversations.android.xmpp.model.Hash;
|
||||||
|
import im.conversations.android.xmpp.model.data.Data;
|
||||||
|
import im.conversations.android.xmpp.model.data.Field;
|
||||||
|
import im.conversations.android.xmpp.model.data.Value;
|
||||||
|
import im.conversations.android.xmpp.model.disco.info.Feature;
|
||||||
|
import im.conversations.android.xmpp.model.disco.info.Identity;
|
||||||
|
import im.conversations.android.xmpp.model.disco.info.InfoQuery;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class EntityCapabilities2 {
|
||||||
|
|
||||||
|
private static final char UNIT_SEPARATOR = 0x1f;
|
||||||
|
private static final char RECORD_SEPARATOR = 0x1e;
|
||||||
|
|
||||||
|
private static final char GROUP_SEPARATOR = 0x1d;
|
||||||
|
|
||||||
|
private static final char FILE_SEPARATOR = 0x1c;
|
||||||
|
|
||||||
|
public static EntityCaps2Hash hash(final InfoQuery info) {
|
||||||
|
return hash(Hash.Algorithm.SHA_256, info);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static EntityCaps2Hash hash(final Hash.Algorithm algorithm, final InfoQuery info) {
|
||||||
|
final String result = algorithm(info);
|
||||||
|
final var hashFunction = toHashFunction(algorithm);
|
||||||
|
return new EntityCaps2Hash(
|
||||||
|
algorithm, hashFunction.hashString(result, StandardCharsets.UTF_8).asBytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static HashFunction toHashFunction(final Hash.Algorithm algorithm) {
|
||||||
|
switch (algorithm) {
|
||||||
|
case SHA_1:
|
||||||
|
return Hashing.sha1();
|
||||||
|
case SHA_256:
|
||||||
|
return Hashing.sha256();
|
||||||
|
case SHA_512:
|
||||||
|
return Hashing.sha512();
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException("Unknown hash algorithm");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String asHex(final String message) {
|
||||||
|
return Joiner.on(' ')
|
||||||
|
.join(
|
||||||
|
Collections2.transform(
|
||||||
|
Bytes.asList(message.getBytes(StandardCharsets.UTF_8)),
|
||||||
|
b -> String.format("%02x", b)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String algorithm(final InfoQuery infoQuery) {
|
||||||
|
return features(infoQuery.getFeatures())
|
||||||
|
+ identities(infoQuery.getIdentities())
|
||||||
|
+ extensions(infoQuery.getExtensions(Data.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String identities(final Collection<Identity> identities) {
|
||||||
|
return Joiner.on("")
|
||||||
|
.join(
|
||||||
|
Ordering.natural()
|
||||||
|
.sortedCopy(
|
||||||
|
Collections2.transform(
|
||||||
|
identities, EntityCapabilities2::identity)))
|
||||||
|
+ FILE_SEPARATOR;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String identity(final Identity identity) {
|
||||||
|
return Strings.nullToEmpty(identity.getCategory())
|
||||||
|
+ UNIT_SEPARATOR
|
||||||
|
+ Strings.nullToEmpty(identity.getType())
|
||||||
|
+ UNIT_SEPARATOR
|
||||||
|
+ Strings.nullToEmpty(identity.getLang())
|
||||||
|
+ UNIT_SEPARATOR
|
||||||
|
+ Strings.nullToEmpty(identity.getIdentityName())
|
||||||
|
+ UNIT_SEPARATOR
|
||||||
|
+ RECORD_SEPARATOR;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String features(Collection<Feature> features) {
|
||||||
|
return Joiner.on("")
|
||||||
|
.join(
|
||||||
|
Ordering.natural()
|
||||||
|
.sortedCopy(
|
||||||
|
Collections2.transform(
|
||||||
|
features, EntityCapabilities2::feature)))
|
||||||
|
+ FILE_SEPARATOR;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String feature(final Feature feature) {
|
||||||
|
return Strings.nullToEmpty(feature.getVar()) + UNIT_SEPARATOR;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String value(final Value value) {
|
||||||
|
return Strings.nullToEmpty(value.getContent()) + UNIT_SEPARATOR;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String values(final Collection<Value> values) {
|
||||||
|
return Joiner.on("")
|
||||||
|
.join(
|
||||||
|
Ordering.natural()
|
||||||
|
.sortedCopy(
|
||||||
|
Collections2.transform(
|
||||||
|
values, EntityCapabilities2::value)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String field(final Field field) {
|
||||||
|
return Strings.nullToEmpty(field.getFieldName())
|
||||||
|
+ UNIT_SEPARATOR
|
||||||
|
+ values(field.getExtensions(Value.class))
|
||||||
|
+ RECORD_SEPARATOR;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String fields(final Collection<Field> fields) {
|
||||||
|
return Joiner.on("")
|
||||||
|
.join(
|
||||||
|
Ordering.natural()
|
||||||
|
.sortedCopy(
|
||||||
|
Collections2.transform(
|
||||||
|
fields, EntityCapabilities2::field)))
|
||||||
|
+ GROUP_SEPARATOR;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String extension(final Data data) {
|
||||||
|
return fields(data.getExtensions(Field.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String extensions(final Collection<Data> extensions) {
|
||||||
|
return Joiner.on("")
|
||||||
|
.join(
|
||||||
|
Ordering.natural()
|
||||||
|
.sortedCopy(
|
||||||
|
Collections2.transform(
|
||||||
|
extensions,
|
||||||
|
EntityCapabilities2::extension)))
|
||||||
|
+ FILE_SEPARATOR;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class EntityCaps2Hash extends EntityCapabilities.Hash {
|
||||||
|
|
||||||
|
public final Hash.Algorithm algorithm;
|
||||||
|
|
||||||
|
protected EntityCaps2Hash(final Hash.Algorithm algorithm, byte[] hash) {
|
||||||
|
super(hash);
|
||||||
|
this.algorithm = algorithm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static EntityCaps2Hash of(final Hash.Algorithm algorithm, final String encoded) {
|
||||||
|
return new EntityCaps2Hash(algorithm, BaseEncoding.base64().decode(encoded));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String capabilityNode(String node) {
|
||||||
|
return String.format(
|
||||||
|
"%s#%s.%s", Namespace.ENTITY_CAPABILITIES_2, algorithm.toString(), encoded());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
if (!super.equals(o)) return false;
|
||||||
|
EntityCaps2Hash that = (EntityCaps2Hash) o;
|
||||||
|
return algorithm == that.algorithm;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(super.hashCode(), algorithm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
package im.conversations.android.xmpp;
|
||||||
|
|
||||||
|
|
||||||
|
import com.google.common.base.MoreObjects;
|
||||||
|
import com.google.common.base.Objects;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.xml.Element;
|
||||||
|
|
||||||
|
import im.conversations.android.xmpp.model.Extension;
|
||||||
|
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
|
||||||
|
public final class ExtensionFactory {
|
||||||
|
|
||||||
|
public static Element create(final String name, final String namespace) {
|
||||||
|
final Class<? extends Extension> clazz = of(name, namespace);
|
||||||
|
if (clazz == null) {
|
||||||
|
return new Element(name, namespace);
|
||||||
|
}
|
||||||
|
final Constructor<? extends Element> constructor;
|
||||||
|
try {
|
||||||
|
constructor = clazz.getDeclaredConstructor();
|
||||||
|
} catch (final NoSuchMethodException e) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
String.format("%s has no default constructor", clazz.getName()),e);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return constructor.newInstance();
|
||||||
|
} catch (final IllegalAccessException
|
||||||
|
| InstantiationException
|
||||||
|
| InvocationTargetException e) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
String.format("%s has inaccessible default constructor", clazz.getName()),e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Class<? extends Extension> of(final String name, final String namespace) {
|
||||||
|
return Extensions.EXTENSION_CLASS_MAP.get(new Id(name, namespace));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Id id(final Class<? extends Extension> clazz) {
|
||||||
|
return Extensions.EXTENSION_CLASS_MAP.inverse().get(clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ExtensionFactory() {}
|
||||||
|
|
||||||
|
public static class Id {
|
||||||
|
public final String name;
|
||||||
|
public final String namespace;
|
||||||
|
|
||||||
|
public Id(String name, String namespace) {
|
||||||
|
this.name = name;
|
||||||
|
this.namespace = namespace;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
Id id = (Id) o;
|
||||||
|
return Objects.equal(name, id.name) && Objects.equal(namespace, id.namespace);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hashCode(name, namespace);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return MoreObjects.toStringHelper(this)
|
||||||
|
.add("name", name)
|
||||||
|
.add("namespace", namespace)
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,112 @@
|
||||||
|
package im.conversations.android.xmpp;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class NodeConfiguration implements Map<String, Object> {
|
||||||
|
|
||||||
|
private static final String PERSIST_ITEMS = "pubsub#persist_items";
|
||||||
|
private static final String ACCESS_MODEL = "pubsub#access_model";
|
||||||
|
private static final String SEND_LAST_PUBLISHED_ITEM = "pubsub#send_last_published_item";
|
||||||
|
private static final String MAX_ITEMS = "pubsub#max_items";
|
||||||
|
private static final String NOTIFY_DELETE = "pubsub#notify_delete";
|
||||||
|
private static final String NOTIFY_RETRACT = "pubsub#notify_retract";
|
||||||
|
|
||||||
|
public static final NodeConfiguration OPEN =
|
||||||
|
new NodeConfiguration(
|
||||||
|
new ImmutableMap.Builder<String, Object>()
|
||||||
|
.put(PERSIST_ITEMS, Boolean.TRUE)
|
||||||
|
.put(ACCESS_MODEL, "open")
|
||||||
|
.build());
|
||||||
|
public static final NodeConfiguration PRESENCE =
|
||||||
|
new NodeConfiguration(
|
||||||
|
new ImmutableMap.Builder<String, Object>()
|
||||||
|
.put(PERSIST_ITEMS, Boolean.TRUE)
|
||||||
|
.put(ACCESS_MODEL, "presence")
|
||||||
|
.build());
|
||||||
|
public static final NodeConfiguration WHITELIST_MAX_ITEMS =
|
||||||
|
new NodeConfiguration(
|
||||||
|
new ImmutableMap.Builder<String, Object>()
|
||||||
|
.put(PERSIST_ITEMS, Boolean.TRUE)
|
||||||
|
.put(ACCESS_MODEL, "whitelist")
|
||||||
|
.put(SEND_LAST_PUBLISHED_ITEM, "never")
|
||||||
|
.put(MAX_ITEMS, "max")
|
||||||
|
.put(NOTIFY_DELETE, Boolean.TRUE)
|
||||||
|
.put(NOTIFY_RETRACT, Boolean.TRUE)
|
||||||
|
.build());
|
||||||
|
private final Map<String, Object> delegate;
|
||||||
|
|
||||||
|
private NodeConfiguration(Map<String, Object> map) {
|
||||||
|
this.delegate = map;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int size() {
|
||||||
|
return this.delegate.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return this.delegate.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsKey(@Nullable Object o) {
|
||||||
|
return this.delegate.containsKey(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean containsValue(@Nullable Object o) {
|
||||||
|
return this.delegate.containsValue(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Object get(@Nullable Object o) {
|
||||||
|
return this.delegate.get(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Object put(String s, Object o) {
|
||||||
|
return this.delegate.put(s, o);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Object remove(@Nullable Object o) {
|
||||||
|
return this.delegate.remove(o);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void putAll(@NonNull Map<? extends String, ?> map) {
|
||||||
|
this.delegate.putAll(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void clear() {
|
||||||
|
this.delegate.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Set<String> keySet() {
|
||||||
|
return this.delegate.keySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Collection<Object> values() {
|
||||||
|
return this.delegate.values();
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public Set<Entry<String, Object>> entrySet() {
|
||||||
|
return this.delegate.entrySet();
|
||||||
|
}
|
||||||
|
}
|
31
src/main/java/im/conversations/android/xmpp/Page.java
Normal file
31
src/main/java/im/conversations/android/xmpp/Page.java
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
package im.conversations.android.xmpp;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import com.google.common.base.MoreObjects;
|
||||||
|
|
||||||
|
public class Page {
|
||||||
|
|
||||||
|
public final String first;
|
||||||
|
public final String last;
|
||||||
|
public final Integer count;
|
||||||
|
|
||||||
|
public Page(String first, String last, Integer count) {
|
||||||
|
this.first = first;
|
||||||
|
this.last = last;
|
||||||
|
this.count = count;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Page emptyWithCount(final String id, final Integer count) {
|
||||||
|
return new Page(id, id, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return MoreObjects.toStringHelper(this)
|
||||||
|
.add("first", first)
|
||||||
|
.add("last", last)
|
||||||
|
.add("count", count)
|
||||||
|
.toString();
|
||||||
|
}
|
||||||
|
}
|
40
src/main/java/im/conversations/android/xmpp/Range.java
Normal file
40
src/main/java/im/conversations/android/xmpp/Range.java
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
package im.conversations.android.xmpp;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import com.google.common.base.MoreObjects;
|
||||||
|
import com.google.common.base.Objects;
|
||||||
|
|
||||||
|
public class Range {
|
||||||
|
|
||||||
|
public final Order order;
|
||||||
|
public final String id;
|
||||||
|
|
||||||
|
public Range(final Order order, final String id) {
|
||||||
|
this.order = order;
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return MoreObjects.toStringHelper(this).add("order", order).add("id", id).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
Range range = (Range) o;
|
||||||
|
return order == range.order && Objects.equal(id, range.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hashCode(order, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Order {
|
||||||
|
NORMAL,
|
||||||
|
REVERSE
|
||||||
|
}
|
||||||
|
}
|
44
src/main/java/im/conversations/android/xmpp/Timestamps.java
Normal file
44
src/main/java/im/conversations/android/xmpp/Timestamps.java
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
package im.conversations.android.xmpp;
|
||||||
|
|
||||||
|
import java.text.ParseException;
|
||||||
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
public final class Timestamps {
|
||||||
|
|
||||||
|
private Timestamps() {
|
||||||
|
throw new IllegalStateException("Do not instantiate me");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static long parse(final String input) throws ParseException {
|
||||||
|
if (input == null) {
|
||||||
|
throw new IllegalArgumentException("timestamp should not be null");
|
||||||
|
}
|
||||||
|
final String timestamp = input.replace("Z", "+0000");
|
||||||
|
final SimpleDateFormat simpleDateFormat =
|
||||||
|
new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.US);
|
||||||
|
final long milliseconds = getMilliseconds(timestamp);
|
||||||
|
final String formatted =
|
||||||
|
timestamp.substring(0, 19) + timestamp.substring(timestamp.length() - 5);
|
||||||
|
final Date date = simpleDateFormat.parse(formatted);
|
||||||
|
if (date == null) {
|
||||||
|
throw new IllegalArgumentException("Date was null");
|
||||||
|
}
|
||||||
|
return date.getTime() + milliseconds;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static long getMilliseconds(final String timestamp) {
|
||||||
|
if (timestamp.length() >= 25 && timestamp.charAt(19) == '.') {
|
||||||
|
final String millis = timestamp.substring(19, timestamp.length() - 5);
|
||||||
|
try {
|
||||||
|
double fractions = Double.parseDouble("0" + millis);
|
||||||
|
return Math.round(1000 * fractions);
|
||||||
|
} catch (final NumberFormatException e) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,18 @@
|
||||||
|
package im.conversations.android.xmpp.model;
|
||||||
|
|
||||||
|
import im.conversations.android.xmpp.model.sasl.SaslError;
|
||||||
|
|
||||||
|
public abstract class AuthenticationFailure extends StreamElement {
|
||||||
|
|
||||||
|
protected AuthenticationFailure(Class<? extends AuthenticationFailure> clazz) {
|
||||||
|
super(clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SaslError getErrorCondition() {
|
||||||
|
return this.getExtension(SaslError.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getText() {
|
||||||
|
return this.findChildContent("text");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package im.conversations.android.xmpp.model;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.crypto.sasl.SaslMechanism;
|
||||||
|
|
||||||
|
public abstract class AuthenticationRequest extends StreamElement{
|
||||||
|
|
||||||
|
|
||||||
|
protected AuthenticationRequest(Class<? extends AuthenticationRequest> clazz) {
|
||||||
|
super(clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract void setMechanism(final SaslMechanism mechanism);
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package im.conversations.android.xmpp.model;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
public abstract class AuthenticationStreamFeature extends StreamFeature{
|
||||||
|
|
||||||
|
public AuthenticationStreamFeature(final Class<? extends AuthenticationStreamFeature> clazz) {
|
||||||
|
super(clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract Collection<String> getMechanismNames();
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package im.conversations.android.xmpp.model;
|
||||||
|
|
||||||
|
import com.google.common.base.CharMatcher;
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
import com.google.common.io.BaseEncoding;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.xml.Element;
|
||||||
|
|
||||||
|
public interface ByteContent {
|
||||||
|
|
||||||
|
String getContent();
|
||||||
|
|
||||||
|
default byte[] asBytes() {
|
||||||
|
final var content = this.getContent();
|
||||||
|
if (Strings.isNullOrEmpty(content)) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
String.format("%s element is lacking content", getClass().getName()));
|
||||||
|
}
|
||||||
|
final var contentCleaned = CharMatcher.whitespace().removeFrom(content);
|
||||||
|
if (BaseEncoding.base64().canDecode(contentCleaned)) {
|
||||||
|
return BaseEncoding.base64().decode(contentCleaned);
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
String.format("%s element contains invalid base64", getClass().getName()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
default void setContent(final byte[] bytes) {
|
||||||
|
setContent(BaseEncoding.base64().encode(bytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
Element setContent(final String content);
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
package im.conversations.android.xmpp.model;
|
||||||
|
|
||||||
|
public abstract class DeliveryReceipt extends Extension {
|
||||||
|
|
||||||
|
protected DeliveryReceipt(Class<? extends Extension> clazz) {
|
||||||
|
super(clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract String getId();
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package im.conversations.android.xmpp.model;
|
||||||
|
|
||||||
|
public abstract class DeliveryReceiptRequest extends Extension {
|
||||||
|
|
||||||
|
protected DeliveryReceiptRequest(Class<? extends Extension> clazz) {
|
||||||
|
super(clazz);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,62 @@
|
||||||
|
package im.conversations.android.xmpp.model;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.collect.Collections2;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.xml.Element;
|
||||||
|
|
||||||
|
import im.conversations.android.xmpp.ExtensionFactory;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
public class Extension extends Element {
|
||||||
|
|
||||||
|
private Extension(final ExtensionFactory.Id id) {
|
||||||
|
super(id.name, id.namespace);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Extension(final Class<? extends Extension> clazz) {
|
||||||
|
this(
|
||||||
|
Preconditions.checkNotNull(
|
||||||
|
ExtensionFactory.id(clazz),
|
||||||
|
String.format(
|
||||||
|
"%s does not seem to be annotated with @XmlElement",
|
||||||
|
clazz.getName())));
|
||||||
|
Preconditions.checkArgument(
|
||||||
|
getClass().equals(clazz), "clazz passed in constructor must match class");
|
||||||
|
}
|
||||||
|
|
||||||
|
public <E extends Extension> boolean hasExtension(final Class<E> clazz) {
|
||||||
|
return Iterables.any(getChildren(), clazz::isInstance);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <E extends Extension> E getExtension(final Class<E> clazz) {
|
||||||
|
final var extension = Iterables.find(getChildren(), clazz::isInstance, null);
|
||||||
|
if (extension == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return clazz.cast(extension);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <E extends Extension> Collection<E> getExtensions(final Class<E> clazz) {
|
||||||
|
return Collections2.transform(
|
||||||
|
Collections2.filter(getChildren(), clazz::isInstance), clazz::cast);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<ExtensionFactory.Id> getExtensionIds() {
|
||||||
|
return Collections2.transform(
|
||||||
|
getChildren(), c -> new ExtensionFactory.Id(c.getName(), c.getNamespace()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T extends Extension> T addExtension(T child) {
|
||||||
|
this.addChild(child);
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addExtensions(final Collection<? extends Extension> extensions) {
|
||||||
|
for (final Extension extension : extensions) {
|
||||||
|
addExtension(extension);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
46
src/main/java/im/conversations/android/xmpp/model/Hash.java
Normal file
46
src/main/java/im/conversations/android/xmpp/model/Hash.java
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
package im.conversations.android.xmpp.model;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import com.google.common.base.CaseFormat;
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.xml.Namespace;
|
||||||
|
import im.conversations.android.annotation.XmlElement;
|
||||||
|
|
||||||
|
@XmlElement(namespace = Namespace.HASHES)
|
||||||
|
public class Hash extends Extension {
|
||||||
|
public Hash() {
|
||||||
|
super(Hash.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Algorithm getAlgorithm() {
|
||||||
|
return Algorithm.tryParse(this.getAttribute("algo"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAlgorithm(final Algorithm algorithm) {
|
||||||
|
this.setAttribute("algo", algorithm.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Algorithm {
|
||||||
|
SHA_1,
|
||||||
|
SHA_256,
|
||||||
|
SHA_512;
|
||||||
|
|
||||||
|
public static Algorithm tryParse(@Nullable final String name) {
|
||||||
|
try {
|
||||||
|
return valueOf(
|
||||||
|
CaseFormat.LOWER_HYPHEN.to(
|
||||||
|
CaseFormat.UPPER_UNDERSCORE, Strings.nullToEmpty(name)));
|
||||||
|
} catch (final IllegalArgumentException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_HYPHEN, super.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package im.conversations.android.xmpp.model;
|
||||||
|
|
||||||
|
public abstract class StreamElement extends Extension {
|
||||||
|
|
||||||
|
protected StreamElement(Class<? extends StreamElement> clazz) {
|
||||||
|
super(clazz);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package im.conversations.android.xmpp.model;
|
||||||
|
|
||||||
|
public abstract class StreamFeature extends Extension{
|
||||||
|
|
||||||
|
public StreamFeature(Class<? extends StreamFeature> clazz) {
|
||||||
|
super(clazz);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package im.conversations.android.xmpp.model.addressing;
|
||||||
|
|
||||||
|
import im.conversations.android.annotation.XmlElement;
|
||||||
|
import im.conversations.android.xmpp.model.Extension;
|
||||||
|
|
||||||
|
@XmlElement
|
||||||
|
public class Address extends Extension {
|
||||||
|
public Address() {
|
||||||
|
super(Address.class);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
package im.conversations.android.xmpp.model.addressing;
|
||||||
|
|
||||||
|
import im.conversations.android.annotation.XmlElement;
|
||||||
|
import im.conversations.android.xmpp.model.Extension;
|
||||||
|
|
||||||
|
@XmlElement
|
||||||
|
public class Addresses extends Extension {
|
||||||
|
public Addresses() {
|
||||||
|
super(Addresses.class);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
@XmlPackage(namespace = Namespace.ADDRESSING)
|
||||||
|
package im.conversations.android.xmpp.model.addressing;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.xml.Namespace;
|
||||||
|
|
||||||
|
import im.conversations.android.annotation.XmlPackage;
|
|
@ -0,0 +1,14 @@
|
||||||
|
package im.conversations.android.xmpp.model.avatar;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.xml.Namespace;
|
||||||
|
import im.conversations.android.annotation.XmlElement;
|
||||||
|
import im.conversations.android.xmpp.model.ByteContent;
|
||||||
|
import im.conversations.android.xmpp.model.Extension;
|
||||||
|
|
||||||
|
@XmlElement(namespace = Namespace.AVATAR_DATA)
|
||||||
|
public class Data extends Extension implements ByteContent {
|
||||||
|
|
||||||
|
public Data() {
|
||||||
|
super(Data.class);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
package im.conversations.android.xmpp.model.avatar;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.xml.Namespace;
|
||||||
|
import im.conversations.android.annotation.XmlElement;
|
||||||
|
import im.conversations.android.xmpp.model.Extension;
|
||||||
|
|
||||||
|
@XmlElement(namespace = Namespace.AVATAR_METADATA)
|
||||||
|
public class Info extends Extension {
|
||||||
|
|
||||||
|
public Info() {
|
||||||
|
super(Info.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getHeight() {
|
||||||
|
return this.getLongAttribute("height");
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getWidth() {
|
||||||
|
return this.getLongAttribute("width");
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getBytes() {
|
||||||
|
return this.getLongAttribute("bytes");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getType() {
|
||||||
|
return this.getAttribute("type");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUrl() {
|
||||||
|
return this.getAttribute("url");
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return this.getAttribute("id");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package im.conversations.android.xmpp.model.avatar;
|
||||||
|
|
||||||
|
import eu.siacs.conversations.xml.Namespace;
|
||||||
|
import im.conversations.android.annotation.XmlElement;
|
||||||
|
import im.conversations.android.xmpp.model.Extension;
|
||||||
|
|
||||||
|
@XmlElement(namespace = Namespace.AVATAR_METADATA)
|
||||||
|
public class Metadata extends Extension {
|
||||||
|
|
||||||
|
public Metadata() {
|
||||||
|
super(Metadata.class);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
package im.conversations.android.xmpp.model.axolotl;
|
||||||
|
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
import im.conversations.android.annotation.XmlElement;
|
||||||
|
import im.conversations.android.xmpp.model.Extension;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import org.whispersystems.libsignal.ecc.ECPublicKey;
|
||||||
|
import org.whispersystems.libsignal.state.PreKeyRecord;
|
||||||
|
|
||||||
|
@XmlElement
|
||||||
|
public class Bundle extends Extension {
|
||||||
|
|
||||||
|
public Bundle() {
|
||||||
|
super(Bundle.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SignedPreKey getSignedPreKey() {
|
||||||
|
return this.getExtension(SignedPreKey.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public SignedPreKeySignature getSignedPreKeySignature() {
|
||||||
|
return this.getExtension(SignedPreKeySignature.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IdentityKey getIdentityKey() {
|
||||||
|
return this.getExtension(IdentityKey.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public PreKey getRandomPreKey() {
|
||||||
|
final var preKeys = this.getExtension(PreKeys.class);
|
||||||
|
final Collection<PreKey> preKeyList =
|
||||||
|
preKeys == null ? Collections.emptyList() : preKeys.getExtensions(PreKey.class);
|
||||||
|
return Iterables.get(preKeyList, (int) (preKeyList.size() * Math.random()), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIdentityKey(final ECPublicKey ecPublicKey) {
|
||||||
|
final var identityKey = this.addExtension(new IdentityKey());
|
||||||
|
identityKey.setContent(ecPublicKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSignedPreKey(
|
||||||
|
final int id, final ECPublicKey ecPublicKey, final byte[] signature) {
|
||||||
|
final var signedPreKey = this.addExtension(new SignedPreKey());
|
||||||
|
signedPreKey.setId(id);
|
||||||
|
signedPreKey.setContent(ecPublicKey);
|
||||||
|
final var signedPreKeySignature = this.addExtension(new SignedPreKeySignature());
|
||||||
|
signedPreKeySignature.setContent(signature);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addPreKeys(final List<PreKeyRecord> preKeyRecords) {
|
||||||
|
final var preKeys = this.addExtension(new PreKeys());
|
||||||
|
for (final PreKeyRecord preKeyRecord : preKeyRecords) {
|
||||||
|
final var preKey = preKeys.addExtension(new PreKey());
|
||||||
|
preKey.setId(preKeyRecord.getId());
|
||||||
|
preKey.setContent(preKeyRecord.getKeyPair().getPublicKey());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package im.conversations.android.xmpp.model.axolotl;
|
||||||
|
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
import com.google.common.primitives.Ints;
|
||||||
|
import im.conversations.android.annotation.XmlElement;
|
||||||
|
import im.conversations.android.xmpp.model.Extension;
|
||||||
|
|
||||||
|
@XmlElement
|
||||||
|
public class Device extends Extension {
|
||||||
|
|
||||||
|
public Device() {
|
||||||
|
super(Device.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getDeviceId() {
|
||||||
|
return Ints.tryParse(Strings.nullToEmpty(this.getAttribute("id")));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDeviceId(int deviceId) {
|
||||||
|
this.setAttribute("id", deviceId);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,35 @@
|
||||||
|
package im.conversations.android.xmpp.model.axolotl;
|
||||||
|
|
||||||
|
import com.google.common.collect.Collections2;
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
import im.conversations.android.annotation.XmlElement;
|
||||||
|
import im.conversations.android.xmpp.model.Extension;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@XmlElement(name = "list")
|
||||||
|
public class DeviceList extends Extension {
|
||||||
|
|
||||||
|
public DeviceList() {
|
||||||
|
super(DeviceList.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<Device> getDevices() {
|
||||||
|
return this.getExtensions(Device.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Set<Integer> getDeviceIds() {
|
||||||
|
return ImmutableSet.copyOf(
|
||||||
|
Collections2.filter(
|
||||||
|
Collections2.transform(getDevices(), Device::getDeviceId),
|
||||||
|
Objects::nonNull));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDeviceIds(Collection<Integer> deviceIds) {
|
||||||
|
for (final Integer deviceId : deviceIds) {
|
||||||
|
final var device = this.addExtension(new Device());
|
||||||
|
device.setDeviceId(deviceId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package im.conversations.android.xmpp.model.axolotl;
|
||||||
|
|
||||||
|
import im.conversations.android.xmpp.model.ByteContent;
|
||||||
|
import org.whispersystems.libsignal.InvalidKeyException;
|
||||||
|
import org.whispersystems.libsignal.ecc.Curve;
|
||||||
|
import org.whispersystems.libsignal.ecc.ECPublicKey;
|
||||||
|
|
||||||
|
public interface ECPublicKeyContent extends ByteContent {
|
||||||
|
|
||||||
|
default ECPublicKey asECPublicKey() {
|
||||||
|
try {
|
||||||
|
return Curve.decodePoint(asBytes(), 0);
|
||||||
|
} catch (InvalidKeyException e) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
String.format("%s does not contain a valid ECPublicKey", getClass().getName()),
|
||||||
|
e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
default void setContent(final ECPublicKey ecPublicKey) {
|
||||||
|
setContent(ecPublicKey.serialize());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package im.conversations.android.xmpp.model.axolotl;
|
||||||
|
|
||||||
|
import im.conversations.android.annotation.XmlElement;
|
||||||
|
import im.conversations.android.xmpp.model.Extension;
|
||||||
|
|
||||||
|
@XmlElement
|
||||||
|
public class Encrypted extends Extension {
|
||||||
|
|
||||||
|
public Encrypted() {
|
||||||
|
super(Encrypted.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasPayload() {
|
||||||
|
return hasExtension(Payload.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Header getHeader() {
|
||||||
|
return getExtension(Header.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Payload getPayload() {
|
||||||
|
return getExtension(Payload.class);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
package im.conversations.android.xmpp.model.axolotl;
|
||||||
|
|
||||||
|
import com.google.common.base.Optional;
|
||||||
|
import com.google.common.collect.Iterables;
|
||||||
|
import im.conversations.android.annotation.XmlElement;
|
||||||
|
import im.conversations.android.xmpp.model.Extension;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
@XmlElement
|
||||||
|
public class Header extends Extension {
|
||||||
|
|
||||||
|
public Header() {
|
||||||
|
super(Header.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addIv(byte[] iv) {
|
||||||
|
this.addExtension(new IV()).setContent(iv);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSourceDevice(long sourceDeviceId) {
|
||||||
|
this.setAttribute("sid", sourceDeviceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<Integer> getSourceDevice() {
|
||||||
|
return getOptionalIntAttribute("sid");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Collection<Key> getKeys() {
|
||||||
|
return this.getExtensions(Key.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Key getKey(final int deviceId) {
|
||||||
|
return Iterables.find(
|
||||||
|
getKeys(), key -> Objects.equals(key.getRemoteDeviceId(), deviceId), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getIv() {
|
||||||
|
final IV iv = this.getExtension(IV.class);
|
||||||
|
if (iv == null) {
|
||||||
|
throw new IllegalStateException("No IV in header");
|
||||||
|
}
|
||||||
|
return iv.asBytes();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package im.conversations.android.xmpp.model.axolotl;
|
||||||
|
|
||||||
|
import im.conversations.android.annotation.XmlElement;
|
||||||
|
import im.conversations.android.xmpp.model.ByteContent;
|
||||||
|
import im.conversations.android.xmpp.model.Extension;
|
||||||
|
|
||||||
|
@XmlElement(name = "iv")
|
||||||
|
public class IV extends Extension implements ByteContent {
|
||||||
|
|
||||||
|
public IV() {
|
||||||
|
super(IV.class);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package im.conversations.android.xmpp.model.axolotl;
|
||||||
|
|
||||||
|
import im.conversations.android.annotation.XmlElement;
|
||||||
|
import im.conversations.android.xmpp.model.Extension;
|
||||||
|
|
||||||
|
@XmlElement(name = "identityKey")
|
||||||
|
public class IdentityKey extends Extension implements ECPublicKeyContent {
|
||||||
|
|
||||||
|
public IdentityKey() {
|
||||||
|
super(IdentityKey.class);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package im.conversations.android.xmpp.model.axolotl;
|
||||||
|
|
||||||
|
import im.conversations.android.annotation.XmlElement;
|
||||||
|
import im.conversations.android.xmpp.model.ByteContent;
|
||||||
|
import im.conversations.android.xmpp.model.Extension;
|
||||||
|
|
||||||
|
@XmlElement
|
||||||
|
public class Key extends Extension implements ByteContent {
|
||||||
|
|
||||||
|
public Key() {
|
||||||
|
super(Key.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setIsPreKey(boolean isPreKey) {
|
||||||
|
this.setAttribute("prekey", isPreKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPreKey() {
|
||||||
|
return this.getAttributeAsBoolean("prekey");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRemoteDeviceId(final int remoteDeviceId) {
|
||||||
|
this.setAttribute("rid", remoteDeviceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getRemoteDeviceId() {
|
||||||
|
return getOptionalIntAttribute("rid").orNull();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package im.conversations.android.xmpp.model.axolotl;
|
||||||
|
|
||||||
|
import im.conversations.android.annotation.XmlElement;
|
||||||
|
import im.conversations.android.xmpp.model.ByteContent;
|
||||||
|
import im.conversations.android.xmpp.model.Extension;
|
||||||
|
|
||||||
|
@XmlElement
|
||||||
|
public class Payload extends Extension implements ByteContent {
|
||||||
|
|
||||||
|
public Payload() {
|
||||||
|
super(Payload.class);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
package im.conversations.android.xmpp.model.axolotl;
|
||||||
|
|
||||||
|
import com.google.common.primitives.Ints;
|
||||||
|
import im.conversations.android.annotation.XmlElement;
|
||||||
|
import im.conversations.android.xmpp.model.Extension;
|
||||||
|
|
||||||
|
@XmlElement(name = "preKeyPublic")
|
||||||
|
public class PreKey extends Extension implements ECPublicKeyContent {
|
||||||
|
|
||||||
|
public PreKey() {
|
||||||
|
super(PreKey.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getId() {
|
||||||
|
return Ints.saturatedCast(this.getLongAttribute("preKeyId"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(int id) {
|
||||||
|
this.setAttribute("preKeyId", id);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package im.conversations.android.xmpp.model.axolotl;
|
||||||
|
|
||||||
|
import im.conversations.android.annotation.XmlElement;
|
||||||
|
import im.conversations.android.xmpp.model.Extension;
|
||||||
|
|
||||||
|
@XmlElement(name = "prekeys")
|
||||||
|
public class PreKeys extends Extension {
|
||||||
|
|
||||||
|
public PreKeys() {
|
||||||
|
super(PreKeys.class);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
package im.conversations.android.xmpp.model.axolotl;
|
||||||
|
|
||||||
|
import com.google.common.primitives.Ints;
|
||||||
|
import im.conversations.android.annotation.XmlElement;
|
||||||
|
import im.conversations.android.xmpp.model.Extension;
|
||||||
|
|
||||||
|
@XmlElement(name = "signedPreKeyPublic")
|
||||||
|
public class SignedPreKey extends Extension implements ECPublicKeyContent {
|
||||||
|
|
||||||
|
public SignedPreKey() {
|
||||||
|
super(SignedPreKey.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getId() {
|
||||||
|
return Ints.saturatedCast(this.getLongAttribute("signedPreKeyId"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(final int id) {
|
||||||
|
this.setAttribute("signedPreKeyId", id);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package im.conversations.android.xmpp.model.axolotl;
|
||||||
|
|
||||||
|
import im.conversations.android.annotation.XmlElement;
|
||||||
|
import im.conversations.android.xmpp.model.ByteContent;
|
||||||
|
import im.conversations.android.xmpp.model.Extension;
|
||||||
|
|
||||||
|
@XmlElement(name = "signedPreKeySignature")
|
||||||
|
public class SignedPreKeySignature extends Extension implements ByteContent {
|
||||||
|
|
||||||
|
public SignedPreKeySignature() {
|
||||||
|
super(SignedPreKeySignature.class);
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue