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 {
|
||||
androidTestImplementation 'tools.fastlane:screengrab:2.1.1'
|
||||
androidTestImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.test:runner:1.3.0'
|
||||
androidTestImplementation 'androidx.test:rules:1.3.0'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
|
||||
androidTestImplementation 'androidx.test:runner:1.6.2'
|
||||
androidTestImplementation 'androidx.test:rules:1.6.1'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
|
||||
|
||||
implementation "androidx.core:core:1.10.1"
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'
|
||||
implementation "androidx.core:core:1.13.1"
|
||||
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'
|
||||
|
||||
|
@ -65,17 +67,17 @@ dependencies {
|
|||
conversationsPlaystoreImplementation("com.android.installreferrer:installreferrer:2.2")
|
||||
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.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.exifinterface:exifinterface:1.3.7'
|
||||
implementation 'androidx.cardview:cardview:1.0.0'
|
||||
implementation "androidx.preference:preference:1.2.1"
|
||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.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"
|
||||
freeImplementation "androidx.emoji2:emoji2-bundled:1.4.0"
|
||||
implementation "androidx.emoji2:emoji2:1.5.0"
|
||||
freeImplementation "androidx.emoji2:emoji2-bundled:1.5.0"
|
||||
|
||||
implementation 'org.bouncycastle:bcmail-jdk18on:1.78.1'
|
||||
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-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.conscrypt:conscrypt-android:2.5.2'
|
||||
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.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 'im.conversations.webrtc:webrtc-android:119.0.1'
|
||||
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.browser:browser:1.8.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
|
||||
|
||||
-keep class eu.siacs.conversations.**
|
||||
-keep class im.conversations.**
|
||||
|
||||
-keep class org.whispersystems.**
|
||||
|
||||
|
|
|
@ -1 +1,3 @@
|
|||
include ':libs:annotation', ':libs:annotation-processor:'
|
||||
|
||||
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>
|
||||
</service>
|
||||
|
||||
<receiver
|
||||
android:name=".receiver.WorkManagerEventReceiver"
|
||||
android:exported="false" />
|
||||
|
||||
<receiver
|
||||
android:name=".receiver.SystemEventReceiver"
|
||||
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 java.security.SecureRandom;
|
||||
|
||||
public class AppSettings {
|
||||
|
||||
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 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;
|
||||
|
||||
public AppSettings(final Context context) {
|
||||
|
@ -143,4 +148,25 @@ public class AppSettings {
|
|||
public boolean isRequireChannelBinding() {
|
||||
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;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Application;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
@ -15,9 +16,17 @@ import eu.siacs.conversations.utils.ThemeHelper;
|
|||
|
||||
public class Conversations extends Application {
|
||||
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
private static Context CONTEXT;
|
||||
|
||||
public static Context getContext() {
|
||||
return Conversations.CONTEXT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
CONTEXT = this.getApplicationContext();
|
||||
ExceptionHelper.init(getApplicationContext());
|
||||
applyThemeSettings();
|
||||
}
|
||||
|
|
|
@ -61,7 +61,6 @@ import eu.siacs.conversations.xml.Element;
|
|||
import eu.siacs.conversations.xml.Namespace;
|
||||
import eu.siacs.conversations.xmpp.Jid;
|
||||
import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded;
|
||||
import eu.siacs.conversations.xmpp.OnIqPacketReceived;
|
||||
import eu.siacs.conversations.xmpp.jingle.DescriptionTransport;
|
||||
import eu.siacs.conversations.xmpp.jingle.OmemoVerification;
|
||||
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.RtpDescription;
|
||||
import eu.siacs.conversations.xmpp.pep.PublishOptions;
|
||||
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
|
||||
import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
|
||||
import im.conversations.android.xmpp.model.stanza.Iq;
|
||||
|
||||
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... ");
|
||||
return;
|
||||
}
|
||||
IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveDeviceIds(account.getJid().asBareJid());
|
||||
mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
|
||||
@Override
|
||||
public void onIqPacketReceived(Account account, IqPacket packet) {
|
||||
if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
|
||||
Iq packet = mXmppConnectionService.getIqGenerator().retrieveDeviceIds(account.getJid().asBareJid());
|
||||
mXmppConnectionService.sendIqPacket(account, packet, response -> {
|
||||
if (response.getType() == Iq.Type.TIMEOUT) {
|
||||
Log.d(Config.LOGTAG, getLogprefix(account) + "Timeout received while retrieving own Device Ids.");
|
||||
} else {
|
||||
//TODO consider calling registerDevices only after item-not-found to account for broken PEPs
|
||||
Element item = mXmppConnectionService.getIqParser().getItem(packet);
|
||||
Set<Integer> deviceIds = mXmppConnectionService.getIqParser().deviceIds(item);
|
||||
final Element item = IqParser.getItem(response);
|
||||
final Set<Integer> deviceIds = IqParser.deviceIds(item);
|
||||
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": retrieved own device list: " + deviceIds);
|
||||
registerDevices(account.getJid().asBareJid(), deviceIds);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -455,12 +451,10 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
|||
|
||||
private void publishDeviceIdsAndRefineAccessModel(final Set<Integer> ids, final boolean firstAttempt) {
|
||||
final Bundle publishOptions = account.getXmppConnection().getFeatures().pepPublishOptions() ? PublishOptions.openAccess() : null;
|
||||
IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(ids, publishOptions);
|
||||
mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() {
|
||||
@Override
|
||||
public void onIqPacketReceived(Account account, IqPacket packet) {
|
||||
final Element error = packet.getType() == IqPacket.TYPE.ERROR ? packet.findChild("error") : null;
|
||||
final boolean preConditionNotMet = PublishOptions.preconditionNotMet(packet);
|
||||
final var publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(ids, publishOptions);
|
||||
mXmppConnectionService.sendIqPacket(account, publish, response -> {
|
||||
final Element error = response.getType() == Iq.Type.ERROR ? response.findChild("error") : null;
|
||||
final boolean preConditionNotMet = PublishOptions.preconditionNotMet(response);
|
||||
if (firstAttempt && preConditionNotMet) {
|
||||
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": precondition wasn't met for device list. pushing node configuration");
|
||||
mXmppConnectionService.pushNodeConfiguration(account, AxolotlService.PEP_DEVICE_LIST, publishOptions, new XmppConnectionService.OnConfigurationPushed() {
|
||||
|
@ -480,17 +474,16 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
|||
account.setOption(Account.OPTION_REQUIRES_ACCESS_MODE_CHANGE, false);
|
||||
mXmppConnectionService.databaseBackend.updateAccount(account);
|
||||
}
|
||||
if (packet.getType() == IqPacket.TYPE.ERROR) {
|
||||
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" + packet.findChild("error"));
|
||||
Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing own device id" + response.findChild("error"));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -506,11 +499,9 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
|||
verifier.initSign(x509PrivateKey, SECURE_RANDOM);
|
||||
verifier.update(axolotlPublicKey.serialize());
|
||||
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());
|
||||
mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
|
||||
@Override
|
||||
public void onIqPacketReceived(final Account account, IqPacket packet) {
|
||||
mXmppConnectionService.sendIqPacket(account, packet, response -> {
|
||||
String node = AxolotlService.PEP_VERIFICATION + ":" + getOwnDeviceId();
|
||||
mXmppConnectionService.pushNodeConfiguration(account, node, PublishOptions.openAccess(), new XmppConnectionService.OnConfigurationPushed() {
|
||||
@Override
|
||||
|
@ -525,7 +516,6 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
|||
publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
|
@ -549,34 +539,32 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
|||
if (this.changeAccessMode.get()) {
|
||||
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": server gained publish-options capabilities. changing access model");
|
||||
}
|
||||
IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(account.getJid().asBareJid(), getOwnDeviceId());
|
||||
mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() {
|
||||
@Override
|
||||
public void onIqPacketReceived(Account account, IqPacket packet) {
|
||||
final Iq packet = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(account.getJid().asBareJid(), getOwnDeviceId());
|
||||
mXmppConnectionService.sendIqPacket(account, packet, response -> {
|
||||
|
||||
if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
|
||||
if (response.getType() == Iq.Type.TIMEOUT) {
|
||||
return; //ignore timeout. do nothing
|
||||
}
|
||||
|
||||
if (packet.getType() == IqPacket.TYPE.ERROR) {
|
||||
Element error = packet.findChild("error");
|
||||
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" + packet);
|
||||
Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "request for device bundles came back with something other than item-not-found" + response);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
PreKeyBundle bundle = mXmppConnectionService.getIqParser().bundle(packet);
|
||||
Map<Integer, ECPublicKey> keys = mXmppConnectionService.getIqParser().preKeyPublics(packet);
|
||||
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:" + packet);
|
||||
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:" + packet);
|
||||
Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received invalid prekeys:" + response);
|
||||
}
|
||||
try {
|
||||
boolean changed = false;
|
||||
|
@ -652,7 +640,6 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
|||
} catch (InvalidKeyException e) {
|
||||
Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Failed to publish bundle " + getOwnDeviceId() + ", reason: " + e.getMessage());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -669,14 +656,12 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
|||
final boolean wipe,
|
||||
final boolean firstAttempt) {
|
||||
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(),
|
||||
preKeyRecords, getOwnDeviceId(), publishOptions);
|
||||
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ": Bundle " + getOwnDeviceId() + " in PEP not current. Publishing...");
|
||||
mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() {
|
||||
@Override
|
||||
public void onIqPacketReceived(final Account account, IqPacket packet) {
|
||||
final boolean preconditionNotMet = PublishOptions.preconditionNotMet(packet);
|
||||
mXmppConnectionService.sendIqPacket(account, publish, response -> {
|
||||
final boolean preconditionNotMet = PublishOptions.preconditionNotMet(response);
|
||||
if (firstAttempt && preconditionNotMet) {
|
||||
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": precondition wasn't met for bundle. pushing node configuration");
|
||||
final String node = AxolotlService.PEP_BUNDLES + ":" + getOwnDeviceId();
|
||||
|
@ -691,7 +676,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
|||
publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe, false);
|
||||
}
|
||||
});
|
||||
} else if (packet.getType() == IqPacket.TYPE.RESULT) {
|
||||
} else if (response.getType() == Iq.Type.RESULT) {
|
||||
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Successfully published bundle. ");
|
||||
if (wipe) {
|
||||
wipeOtherPepDevices();
|
||||
|
@ -699,15 +684,14 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
|||
Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId());
|
||||
publishOwnDeviceIdIfNeeded();
|
||||
}
|
||||
} else if (packet.getType() == IqPacket.TYPE.ERROR) {
|
||||
} 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: " + packet.toString());
|
||||
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);
|
||||
}
|
||||
final SettableFuture<XmppAxolotlSession> future = SettableFuture.create();
|
||||
final IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveVerificationForDevice(jid, address.getDeviceId());
|
||||
mXmppConnectionService.sendIqPacket(account, packet, (account, response) -> {
|
||||
Pair<X509Certificate[], byte[]> verification = mXmppConnectionService.getIqParser().verification(response);
|
||||
final Iq packet = mXmppConnectionService.getIqGenerator().retrieveVerificationForDevice(jid, address.getDeviceId());
|
||||
mXmppConnectionService.sendIqPacket(account, packet, response -> {
|
||||
Pair<X509Certificate[], byte[]> verification = IqParser.verification(response);
|
||||
if (verification != null) {
|
||||
try {
|
||||
Signature verifier = Signature.getInstance("sha256WithRSA");
|
||||
|
@ -846,7 +830,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
|||
}
|
||||
|
||||
private void fetchDeviceIds(final Jid jid, OnDeviceIdsFetched callback) {
|
||||
IqPacket packet;
|
||||
final Iq packet;
|
||||
synchronized (this.fetchDeviceIdsMap) {
|
||||
List<OnDeviceIdsFetched> callbacks = this.fetchDeviceIdsMap.get(jid);
|
||||
if (callbacks != null) {
|
||||
|
@ -866,11 +850,11 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
|||
}
|
||||
}
|
||||
if (packet != null) {
|
||||
mXmppConnectionService.sendIqPacket(account, packet, (account, response) -> {
|
||||
if (response.getType() == IqPacket.TYPE.RESULT) {
|
||||
mXmppConnectionService.sendIqPacket(account, packet, response -> {
|
||||
if (response.getType() == Iq.Type.RESULT) {
|
||||
fetchDeviceListStatus.put(jid, true);
|
||||
Element item = mXmppConnectionService.getIqParser().getItem(response);
|
||||
Set<Integer> deviceIds = mXmppConnectionService.getIqParser().deviceIds(item);
|
||||
final Element item = IqParser.getItem(response);
|
||||
final Set<Integer> deviceIds = IqParser.deviceIds(item);
|
||||
registerDevices(jid, deviceIds);
|
||||
final List<OnDeviceIdsFetched> callbacks;
|
||||
synchronized (fetchDeviceIdsMap) {
|
||||
|
@ -882,7 +866,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
|||
}
|
||||
}
|
||||
} else {
|
||||
if (response.getType() == IqPacket.TYPE.TIMEOUT) {
|
||||
if (response.getType() == Iq.Type.TIMEOUT) {
|
||||
fetchDeviceListStatus.remove(jid);
|
||||
} else {
|
||||
fetchDeviceListStatus.put(jid, false);
|
||||
|
@ -929,16 +913,15 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
|||
}
|
||||
final Jid jid = Jid.of(address.getName());
|
||||
final boolean oneOfOurs = jid.asBareJid().equals(account.getJid().asBareJid());
|
||||
IqPacket bundlesPacket = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(jid, address.getDeviceId());
|
||||
mXmppConnectionService.sendIqPacket(account, bundlesPacket, (account, packet) -> {
|
||||
if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
|
||||
final Iq bundlesPacket = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(jid, address.getDeviceId());
|
||||
mXmppConnectionService.sendIqPacket(account, bundlesPacket, packet -> {
|
||||
if (packet.getType() == Iq.Type.TIMEOUT) {
|
||||
fetchStatusMap.put(address, FetchStatus.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...");
|
||||
final IqParser parser = mXmppConnectionService.getIqParser();
|
||||
final List<PreKeyBundle> preKeyBundleList = parser.preKeys(packet);
|
||||
final PreKeyBundle bundle = parser.bundle(packet);
|
||||
final List<PreKeyBundle> preKeyBundleList = IqParser.preKeys(packet);
|
||||
final PreKeyBundle bundle = IqParser.bundle(packet);
|
||||
if (preKeyBundleList.isEmpty() || bundle == null) {
|
||||
Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "preKey IQ packet invalid: " + packet);
|
||||
fetchStatusMap.put(address, FetchStatus.ERROR);
|
||||
|
@ -1544,7 +1527,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
|
|||
axolotlMessage.addDevice(session, true);
|
||||
try {
|
||||
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);
|
||||
} catch (IllegalArgumentException 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.Option;
|
||||
import eu.siacs.conversations.xmpp.mam.MamReference;
|
||||
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
|
||||
|
||||
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 static final String TABLENAME = "conversations";
|
||||
|
@ -1600,7 +1600,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
|||
show();
|
||||
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"));
|
||||
final Element c = packet.addChild("command", Namespace.COMMANDS);
|
||||
c.setAttribute("node", command.getAttribute("node"));
|
||||
|
@ -1618,7 +1618,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
|||
}
|
||||
}, 1000);
|
||||
} else {
|
||||
xmppConnectionService.sendIqPacket(getAccount(), packet, (a, iq) -> {
|
||||
xmppConnectionService.sendIqPacket(getAccount(), packet, (iq) -> {
|
||||
session.updateWithResponse(iq);
|
||||
}, 120L);
|
||||
}
|
||||
|
@ -1645,7 +1645,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
|||
|
||||
public void startMucConfig(XmppConnectionService 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.addChild("query", "http://jabber.org/protocol/muc#owner");
|
||||
|
||||
|
@ -1661,7 +1661,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
|||
}
|
||||
}, 1000);
|
||||
} else {
|
||||
xmppConnectionService.sendIqPacket(getAccount(), packet, (a, iq) -> {
|
||||
xmppConnectionService.sendIqPacket(getAccount(), packet, (iq) -> {
|
||||
session.updateWithResponse(iq);
|
||||
}, 120L);
|
||||
}
|
||||
|
@ -2782,7 +2782,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
|||
protected Item mkItem(Element el, int pos) {
|
||||
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")) {
|
||||
viewType = TYPE_NOTE;
|
||||
} 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 mNode;
|
||||
protected CommandPageBinding mBinding = null;
|
||||
protected IqPacket response = null;
|
||||
protected Iq response = null;
|
||||
protected Element responseElement = null;
|
||||
protected boolean expectingRemoval = false;
|
||||
protected List<Field> reported = null;
|
||||
|
@ -2893,7 +2893,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
|||
protected GridLayoutManager layoutManager;
|
||||
protected WebView actionToWebview = null;
|
||||
protected int fillableFieldCount = 0;
|
||||
protected IqPacket pendingResponsePacket = null;
|
||||
protected Iq pendingResponsePacket = null;
|
||||
protected boolean waitingForRefresh = false;
|
||||
|
||||
CommandSession(String title, String node, XmppConnectionService xmppConnectionService) {
|
||||
|
@ -2912,7 +2912,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
|||
return mNode;
|
||||
}
|
||||
|
||||
public void updateWithResponse(final IqPacket iq) {
|
||||
public void updateWithResponse(final Iq iq) {
|
||||
if (getView() != null && getView().isAttachedToWindow()) {
|
||||
getView().post(() -> updateWithResponseUiThread(iq));
|
||||
} 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;
|
||||
this.loadingTimer = new Timer();
|
||||
oldTimer.cancel();
|
||||
|
@ -2937,7 +2937,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
|||
|
||||
boolean actionsCleared = false;
|
||||
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")) {
|
||||
xmppConnectionService.createContact(getAccount().getRoster().getContact(iq.getFrom()), true);
|
||||
}
|
||||
|
@ -3101,7 +3101,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
|||
public int getItemCount() {
|
||||
if (loading) return 1;
|
||||
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;
|
||||
for (Element el : responseElement.getChildren()) {
|
||||
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 (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")) {
|
||||
int i = 0;
|
||||
for (Element el : responseElement.getChildren()) {
|
||||
|
@ -3317,7 +3317,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
|||
return false;
|
||||
}
|
||||
|
||||
final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
|
||||
final var packet = new Iq(Iq.Type.SET);
|
||||
packet.setTo(response.getFrom());
|
||||
final Element c = packet.addChild("command", Namespace.COMMANDS);
|
||||
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);
|
||||
|
||||
executing = true;
|
||||
xmppConnectionService.sendIqPacket(getAccount(), packet, (a, iq) -> {
|
||||
xmppConnectionService.sendIqPacket(getAccount(), packet, (iq) -> {
|
||||
updateWithResponse(iq);
|
||||
}, 120L);
|
||||
|
||||
|
@ -3495,7 +3495,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
|||
actionsAdapter.notifyDataSetChanged();
|
||||
|
||||
if (pendingResponsePacket != null) {
|
||||
final IqPacket pending = pendingResponsePacket;
|
||||
final var pending = pendingResponsePacket;
|
||||
pendingResponsePacket = null;
|
||||
updateWithResponseUiThread(pending);
|
||||
}
|
||||
|
@ -3570,7 +3570,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
|||
}
|
||||
|
||||
@Override
|
||||
protected void updateWithResponseUiThread(final IqPacket iq) {
|
||||
protected void updateWithResponseUiThread(final Iq iq) {
|
||||
Timer oldTimer = this.loadingTimer;
|
||||
this.loadingTimer = new Timer();
|
||||
oldTimer.cancel();
|
||||
|
@ -3586,7 +3586,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
|||
layoutManager.setSpanCount(1);
|
||||
|
||||
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 String title = form.getTitle();
|
||||
if (title != null) {
|
||||
|
@ -3605,7 +3605,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
|||
if (actionsAdapter.getPosition("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;
|
||||
removeSession(this);
|
||||
return;
|
||||
|
@ -3619,7 +3619,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
|||
@Override
|
||||
public synchronized boolean execute(String 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());
|
||||
final Element form = packet
|
||||
.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;
|
||||
|
||||
final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
|
||||
final var packet = new Iq(Iq.Type.SET);
|
||||
packet.setTo(response.getFrom());
|
||||
|
||||
String formType = responseElement == null ? null : responseElement.getAttribute("type");
|
||||
|
@ -3647,7 +3647,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
|
|||
}
|
||||
|
||||
executing = true;
|
||||
xmppConnectionService.sendIqPacket(getAccount(), packet, (a, iq) -> {
|
||||
xmppConnectionService.sendIqPacket(getAccount(), packet, (iq) -> {
|
||||
updateWithResponse(iq);
|
||||
}, 120L);
|
||||
|
||||
|
|
|
@ -29,6 +29,14 @@ import eu.siacs.conversations.xmpp.forms.Field;
|
|||
import eu.siacs.conversations.xmpp.pep.Avatar;
|
||||
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 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() {
|
||||
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.xmpp.forms.Data;
|
||||
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 static final String TABLENAME = "discovery_results";
|
||||
|
@ -36,7 +36,7 @@ public class ServiceDiscoveryResult {
|
|||
protected final List<String> features;
|
||||
protected final List<Data> forms;
|
||||
private final List<Identity> identities;
|
||||
public ServiceDiscoveryResult(final IqPacket packet) {
|
||||
public ServiceDiscoveryResult(final Iq packet) {
|
||||
this.identities = new ArrayList<>();
|
||||
this.features = new ArrayList<>();
|
||||
this.forms = new ArrayList<>();
|
||||
|
@ -279,7 +279,7 @@ public class ServiceDiscoveryResult {
|
|||
return values;
|
||||
}
|
||||
|
||||
public static class Identity implements Comparable {
|
||||
public static class Identity implements Comparable<Identity> {
|
||||
protected final String type;
|
||||
protected final String lang;
|
||||
protected final String name;
|
||||
|
@ -327,8 +327,21 @@ public class ServiceDiscoveryResult {
|
|||
return this.name;
|
||||
}
|
||||
|
||||
public int compareTo(@NonNull Object other) {
|
||||
Identity o = (Identity) other;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(final Identity o) {
|
||||
int r = blankNull(this.getCategory()).compareTo(blankNull(o.getCategory()));
|
||||
if (r == 0) {
|
||||
r = blankNull(this.getType()).compareTo(blankNull(o.getType()));
|
||||
|
@ -342,18 +355,5 @@ public class ServiceDiscoveryResult {
|
|||
|
||||
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.forms.Data;
|
||||
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 {
|
||||
|
||||
|
@ -53,8 +53,8 @@ public class IqGenerator extends AbstractGenerator {
|
|||
super(service);
|
||||
}
|
||||
|
||||
public IqPacket discoResponse(final Account account, final IqPacket request) {
|
||||
final IqPacket packet = new IqPacket(IqPacket.TYPE.RESULT);
|
||||
public Iq discoResponse(final Account account, final Iq request) {
|
||||
final var packet = new Iq(Iq.Type.RESULT);
|
||||
packet.setId(request.getId());
|
||||
packet.setTo(request.getFrom());
|
||||
final Element query = packet.addChild("query", "http://jabber.org/protocol/disco#info");
|
||||
|
@ -69,8 +69,8 @@ public class IqGenerator extends AbstractGenerator {
|
|||
return packet;
|
||||
}
|
||||
|
||||
public IqPacket versionResponse(final IqPacket request) {
|
||||
final IqPacket packet = request.generateResponse(IqPacket.TYPE.RESULT);
|
||||
public Iq versionResponse(final Iq request) {
|
||||
final var packet = request.generateResponse(Iq.Type.RESULT);
|
||||
Element query = packet.query("jabber:iq:version");
|
||||
query.addChild("name").setContent(mXmppConnectionService.getString(R.string.app_name));
|
||||
query.addChild("version").setContent(getIdentityVersion());
|
||||
|
@ -93,8 +93,8 @@ public class IqGenerator extends AbstractGenerator {
|
|||
return packet;
|
||||
}
|
||||
|
||||
public IqPacket entityTimeResponse(IqPacket request) {
|
||||
final IqPacket packet = request.generateResponse(IqPacket.TYPE.RESULT);
|
||||
public Iq entityTimeResponse(final Iq request) {
|
||||
final Iq packet = request.generateResponse(Iq.Type.RESULT);
|
||||
Element time = packet.addChild("time", "urn:xmpp:time");
|
||||
final long now = System.currentTimeMillis();
|
||||
time.addChild("utc").setContent(getTimestamp(now));
|
||||
|
@ -113,14 +113,14 @@ public class IqGenerator extends AbstractGenerator {
|
|||
return packet;
|
||||
}
|
||||
|
||||
public IqPacket purgeOfflineMessages() {
|
||||
final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
|
||||
public static Iq purgeOfflineMessages() {
|
||||
final Iq packet = new Iq(Iq.Type.SET);
|
||||
packet.addChild("offline", Namespace.FLEXIBLE_OFFLINE_MESSAGE_RETRIEVAL).addChild("purge");
|
||||
return packet;
|
||||
}
|
||||
|
||||
protected IqPacket publish(final String node, final Element item, final Bundle options) {
|
||||
final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
|
||||
protected Iq publish(final String node, final Element item, final Bundle options) {
|
||||
final var packet = new Iq(Iq.Type.SET);
|
||||
final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB);
|
||||
final Element publish = pubsub.addChild("publish");
|
||||
publish.setAttribute("node", node);
|
||||
|
@ -132,12 +132,12 @@ public class IqGenerator extends AbstractGenerator {
|
|||
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);
|
||||
}
|
||||
|
||||
private IqPacket retrieve(String node, Element item) {
|
||||
final IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
|
||||
private Iq retrieve(String node, Element item) {
|
||||
final var packet = new Iq(Iq.Type.GET);
|
||||
final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB);
|
||||
final Element items = pubsub.addChild("items");
|
||||
items.setAttribute("node", node);
|
||||
|
@ -147,36 +147,36 @@ public class IqGenerator extends AbstractGenerator {
|
|||
return packet;
|
||||
}
|
||||
|
||||
public IqPacket retrieveVcard4(final Jid jid) {
|
||||
final IqPacket packet = retrieve("urn:xmpp:vcard4", null);
|
||||
public Iq retrieveVcard4(final Jid jid) {
|
||||
final var packet = retrieve("urn:xmpp:vcard4", null);
|
||||
packet.setTo(jid);
|
||||
return packet;
|
||||
}
|
||||
|
||||
public IqPacket retrieveBookmarks() {
|
||||
public Iq retrieveBookmarks() {
|
||||
return retrieve(Namespace.BOOKMARKS2, null);
|
||||
}
|
||||
|
||||
public IqPacket retrieveMds() {
|
||||
public Iq retrieveMds() {
|
||||
return retrieve(Namespace.MDS_DISPLAYED, null);
|
||||
}
|
||||
|
||||
public IqPacket publishNick(String nick) {
|
||||
public Iq publishNick(String nick) {
|
||||
final Element item = new Element("item");
|
||||
item.setAttribute("id", "current");
|
||||
item.addChild("nick", Namespace.NICK).setContent(nick);
|
||||
return publish(Namespace.NICK, item);
|
||||
}
|
||||
|
||||
public IqPacket deleteNode(final String node) {
|
||||
final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
|
||||
public Iq deleteNode(final String node) {
|
||||
final var packet = new Iq(Iq.Type.SET);
|
||||
final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB_OWNER);
|
||||
pubsub.addChild("delete").setAttribute("node", node);
|
||||
return packet;
|
||||
}
|
||||
|
||||
public IqPacket deleteItem(final String node, final String id) {
|
||||
IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
|
||||
public Iq deleteItem(final String node, final String id) {
|
||||
final var packet = new Iq(Iq.Type.SET);
|
||||
final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB);
|
||||
final Element retract = pubsub.addChild("retract");
|
||||
retract.setAttribute("node", node);
|
||||
|
@ -185,7 +185,7 @@ public class IqGenerator extends AbstractGenerator {
|
|||
return packet;
|
||||
}
|
||||
|
||||
public IqPacket publishAvatar(Avatar avatar, Bundle options) {
|
||||
public Iq publishAvatar(Avatar avatar, Bundle options) {
|
||||
final Element item = new Element("item");
|
||||
item.setAttribute("id", avatar.sha1sum);
|
||||
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);
|
||||
}
|
||||
|
||||
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");
|
||||
item.setAttribute("id", id);
|
||||
item.addChild(element);
|
||||
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");
|
||||
item.setAttribute("id", avatar.sha1sum);
|
||||
final Element metadata = item
|
||||
|
@ -214,57 +214,57 @@ public class IqGenerator extends AbstractGenerator {
|
|||
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");
|
||||
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);
|
||||
return packet;
|
||||
}
|
||||
|
||||
public IqPacket retrieveVcardAvatar(final Avatar avatar) {
|
||||
final IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
|
||||
public Iq retrieveVcardAvatar(final Avatar avatar) {
|
||||
final Iq packet = new Iq(Iq.Type.GET);
|
||||
packet.setTo(avatar.owner);
|
||||
packet.addChild("vCard", "vcard-temp");
|
||||
return packet;
|
||||
}
|
||||
|
||||
public IqPacket retrieveVcardAvatar(final Jid to) {
|
||||
final IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
|
||||
public Iq retrieveVcardAvatar(final Jid to) {
|
||||
final Iq packet = new Iq(Iq.Type.GET);
|
||||
packet.setTo(to);
|
||||
packet.addChild("vCard", "vcard-temp");
|
||||
return packet;
|
||||
}
|
||||
|
||||
public IqPacket retrieveAvatarMetaData(final Jid to) {
|
||||
final IqPacket packet = retrieve("urn:xmpp:avatar:metadata", null);
|
||||
public Iq retrieveAvatarMetaData(final Jid to) {
|
||||
final Iq packet = retrieve("urn:xmpp:avatar:metadata", null);
|
||||
if (to != null) {
|
||||
packet.setTo(to);
|
||||
}
|
||||
return packet;
|
||||
}
|
||||
|
||||
public IqPacket retrieveDeviceIds(final Jid to) {
|
||||
final IqPacket packet = retrieve(AxolotlService.PEP_DEVICE_LIST, null);
|
||||
public Iq retrieveDeviceIds(final Jid to) {
|
||||
final var packet = retrieve(AxolotlService.PEP_DEVICE_LIST, null);
|
||||
if (to != null) {
|
||||
packet.setTo(to);
|
||||
}
|
||||
return packet;
|
||||
}
|
||||
|
||||
public IqPacket retrieveBundlesForDevice(final Jid to, final int deviceid) {
|
||||
final IqPacket packet = retrieve(AxolotlService.PEP_BUNDLES + ":" + deviceid, null);
|
||||
public Iq retrieveBundlesForDevice(final Jid to, final int deviceid) {
|
||||
final var packet = retrieve(AxolotlService.PEP_BUNDLES + ":" + deviceid, null);
|
||||
packet.setTo(to);
|
||||
return packet;
|
||||
}
|
||||
|
||||
public IqPacket retrieveVerificationForDevice(final Jid to, final int deviceid) {
|
||||
final IqPacket packet = retrieve(AxolotlService.PEP_VERIFICATION + ":" + deviceid, null);
|
||||
public Iq retrieveVerificationForDevice(final Jid to, final int deviceid) {
|
||||
final var packet = retrieve(AxolotlService.PEP_VERIFICATION + ":" + deviceid, null);
|
||||
packet.setTo(to);
|
||||
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");
|
||||
item.setAttribute("id", "current");
|
||||
final Element list = item.addChild("list", AxolotlService.PEP_PREFIX);
|
||||
|
@ -314,7 +314,7 @@ public class IqGenerator extends AbstractGenerator {
|
|||
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 Element item = new Element("item");
|
||||
item.setAttribute("id", "current");
|
||||
|
@ -338,7 +338,7 @@ public class IqGenerator extends AbstractGenerator {
|
|||
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");
|
||||
item.setAttribute("id", "current");
|
||||
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);
|
||||
}
|
||||
|
||||
public IqPacket queryMessageArchiveManagement(final MessageArchiveService.Query mam) {
|
||||
final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
|
||||
public Iq queryMessageArchiveManagement(final MessageArchiveService.Query mam) {
|
||||
final Iq packet = new Iq(Iq.Type.SET);
|
||||
final Element query = packet.query(mam.version.namespace);
|
||||
query.setAttribute("queryid", mam.getQueryId());
|
||||
final Data data = new Data();
|
||||
|
@ -387,15 +387,15 @@ public class IqGenerator extends AbstractGenerator {
|
|||
return packet;
|
||||
}
|
||||
|
||||
public IqPacket generateGetBlockList() {
|
||||
final IqPacket iq = new IqPacket(IqPacket.TYPE.GET);
|
||||
public Iq generateGetBlockList() {
|
||||
final Iq iq = new Iq(Iq.Type.GET);
|
||||
iq.addChild("blocklist", Namespace.BLOCKING);
|
||||
|
||||
return iq;
|
||||
}
|
||||
|
||||
public IqPacket generateSetBlockRequest(final Jid jid, final boolean reportSpam, final String serverMsgId) {
|
||||
final IqPacket iq = new IqPacket(IqPacket.TYPE.SET);
|
||||
public Iq generateSetBlockRequest(final Jid jid, final boolean reportSpam, final String serverMsgId) {
|
||||
final Iq iq = new Iq(Iq.Type.SET);
|
||||
final Element block = iq.addChild("block", Namespace.BLOCKING);
|
||||
final Element item = block.addChild("item").setAttribute("jid", jid);
|
||||
if (reportSpam) {
|
||||
|
@ -411,15 +411,15 @@ public class IqGenerator extends AbstractGenerator {
|
|||
return iq;
|
||||
}
|
||||
|
||||
public IqPacket generateSetUnblockRequest(final Jid jid) {
|
||||
final IqPacket iq = new IqPacket(IqPacket.TYPE.SET);
|
||||
public Iq generateSetUnblockRequest(final Jid jid) {
|
||||
final Iq iq = new Iq(Iq.Type.SET);
|
||||
final Element block = iq.addChild("unblock", Namespace.BLOCKING);
|
||||
block.addChild("item").setAttribute("jid", jid);
|
||||
return iq;
|
||||
}
|
||||
|
||||
public IqPacket generateSetPassword(final Account account, final String newPassword) {
|
||||
final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
|
||||
public Iq generateSetPassword(final Account account, final String newPassword) {
|
||||
final Iq packet = new Iq(Iq.Type.SET);
|
||||
packet.setTo(account.getDomain());
|
||||
final Element query = packet.addChild("query", Namespace.REGISTER);
|
||||
final Jid jid = account.getJid();
|
||||
|
@ -428,14 +428,14 @@ public class IqGenerator extends AbstractGenerator {
|
|||
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<>();
|
||||
jids.add(jid);
|
||||
return changeAffiliation(conference, jids, affiliation);
|
||||
}
|
||||
|
||||
public IqPacket changeAffiliation(Conversation conference, List<Jid> jids, String affiliation) {
|
||||
IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
|
||||
public Iq changeAffiliation(Conversation conference, List<Jid> jids, String affiliation) {
|
||||
final Iq packet = new Iq(Iq.Type.SET);
|
||||
packet.setTo(conference.getJid().asBareJid());
|
||||
packet.setFrom(conference.getAccount().getJid());
|
||||
Element query = packet.query("http://jabber.org/protocol/muc#admin");
|
||||
|
@ -447,8 +447,8 @@ public class IqGenerator extends AbstractGenerator {
|
|||
return packet;
|
||||
}
|
||||
|
||||
public IqPacket changeRole(Conversation conference, String nick, String role) {
|
||||
IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
|
||||
public Iq changeRole(Conversation conference, String nick, String role) {
|
||||
final Iq packet = new Iq(Iq.Type.SET);
|
||||
packet.setTo(conference.getJid().asBareJid());
|
||||
packet.setFrom(conference.getAccount().getJid());
|
||||
Element item = packet.query("http://jabber.org/protocol/muc#admin").addChild("item");
|
||||
|
@ -457,11 +457,11 @@ public class IqGenerator extends AbstractGenerator {
|
|||
return packet;
|
||||
}
|
||||
|
||||
public IqPacket moderateMessage(Account account, Message m, String reason) {
|
||||
IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
|
||||
public Iq moderateMessage(Account account, Message m, String reason) {
|
||||
final var packet = new Iq(Iq.Type.SET);
|
||||
packet.setTo(m.getConversation().getJid().asBareJid());
|
||||
packet.setFrom(account.getJid());
|
||||
Element moderate =
|
||||
final var moderate =
|
||||
packet.addChild("apply-to", "urn:xmpp:fasten:0")
|
||||
.setAttribute("id", m.getServerMsgId())
|
||||
.addChild("moderate", "urn:xmpp:message-moderate:0");
|
||||
|
@ -470,8 +470,8 @@ public class IqGenerator extends AbstractGenerator {
|
|||
return packet;
|
||||
}
|
||||
|
||||
public IqPacket requestHttpUploadSlot(Jid host, DownloadableFile file, String name, String mime) {
|
||||
IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
|
||||
public Iq requestHttpUploadSlot(Jid host, DownloadableFile file, String name, String mime) {
|
||||
final Iq packet = new Iq(Iq.Type.GET);
|
||||
packet.setTo(host);
|
||||
Element request = packet.addChild("request", Namespace.HTTP_UPLOAD);
|
||||
request.setAttribute("filename", name == null ? convertFilename(file.getName()) : name);
|
||||
|
@ -480,8 +480,8 @@ public class IqGenerator extends AbstractGenerator {
|
|||
return packet;
|
||||
}
|
||||
|
||||
public IqPacket requestHttpUploadLegacySlot(Jid host, DownloadableFile file, String mime) {
|
||||
IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
|
||||
public Iq requestHttpUploadLegacySlot(Jid host, DownloadableFile file, String mime) {
|
||||
final Iq packet = new Iq(Iq.Type.GET);
|
||||
packet.setTo(host);
|
||||
Element request = packet.addChild("request", Namespace.HTTP_UPLOAD_LEGACY);
|
||||
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) {
|
||||
final IqPacket register = new IqPacket(IqPacket.TYPE.SET);
|
||||
public static Iq generateCreateAccountWithCaptcha(final Account account, final String id, final Data data) {
|
||||
final Iq register = new Iq(Iq.Type.SET);
|
||||
register.setFrom(account.getJid().asBareJid());
|
||||
register.setTo(account.getDomain());
|
||||
register.setId(id);
|
||||
|
@ -519,12 +519,12 @@ public class IqGenerator extends AbstractGenerator {
|
|||
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);
|
||||
}
|
||||
|
||||
public IqPacket pushTokenToAppServer(Jid appServer, String token, String deviceId, Jid muc) {
|
||||
final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
|
||||
public Iq pushTokenToAppServer(Jid appServer, String token, String deviceId, Jid muc) {
|
||||
final Iq packet = new Iq(Iq.Type.SET);
|
||||
packet.setTo(appServer);
|
||||
final Element command = packet.addChild("command", Namespace.COMMANDS);
|
||||
command.setAttribute("node", "register-push-fcm");
|
||||
|
@ -540,8 +540,8 @@ public class IqGenerator extends AbstractGenerator {
|
|||
return packet;
|
||||
}
|
||||
|
||||
public IqPacket unregisterChannelOnAppServer(Jid appServer, String deviceId, String channel) {
|
||||
final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
|
||||
public Iq unregisterChannelOnAppServer(Jid appServer, String deviceId, String channel) {
|
||||
final Iq packet = new Iq(Iq.Type.SET);
|
||||
packet.setTo(appServer);
|
||||
final Element command = packet.addChild("command", Namespace.COMMANDS);
|
||||
command.setAttribute("node", "unregister-push-fcm");
|
||||
|
@ -554,8 +554,8 @@ public class IqGenerator extends AbstractGenerator {
|
|||
return packet;
|
||||
}
|
||||
|
||||
public IqPacket enablePush(final Jid jid, final String node, final String secret) {
|
||||
IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
|
||||
public Iq enablePush(final Jid jid, final String node, final String secret) {
|
||||
final Iq packet = new Iq(Iq.Type.SET);
|
||||
Element enable = packet.addChild("enable", Namespace.PUSH);
|
||||
enable.setAttribute("jid", jid);
|
||||
enable.setAttribute("node", node);
|
||||
|
@ -569,16 +569,16 @@ public class IqGenerator extends AbstractGenerator {
|
|||
return packet;
|
||||
}
|
||||
|
||||
public IqPacket disablePush(final Jid jid, final String node) {
|
||||
IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
|
||||
public Iq disablePush(final Jid jid, final String node) {
|
||||
Iq packet = new Iq(Iq.Type.SET);
|
||||
Element disable = packet.addChild("disable", Namespace.PUSH);
|
||||
disable.setAttribute("jid", jid);
|
||||
disable.setAttribute("node", node);
|
||||
return packet;
|
||||
}
|
||||
|
||||
public IqPacket queryAffiliation(Conversation conversation, String affiliation) {
|
||||
IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
|
||||
public Iq queryAffiliation(Conversation conversation, String affiliation) {
|
||||
final Iq packet = new Iq(Iq.Type.GET);
|
||||
packet.setTo(conversation.getJid().asBareJid());
|
||||
packet.query("http://jabber.org/protocol/muc#admin").addChild("item").setAttribute("affiliation", affiliation);
|
||||
return packet;
|
||||
|
@ -611,16 +611,16 @@ public class IqGenerator extends AbstractGenerator {
|
|||
return options;
|
||||
}
|
||||
|
||||
public IqPacket requestPubsubConfiguration(Jid jid, String node) {
|
||||
public Iq requestPubsubConfiguration(Jid jid, String node) {
|
||||
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);
|
||||
}
|
||||
|
||||
private IqPacket pubsubConfiguration(Jid jid, String node, Data data) {
|
||||
IqPacket packet = new IqPacket(data == null ? IqPacket.TYPE.GET : IqPacket.TYPE.SET);
|
||||
private Iq pubsubConfiguration(Jid jid, String node, Data data) {
|
||||
final Iq packet = new Iq(data == null ? Iq.Type.GET : Iq.Type.SET);
|
||||
packet.setTo(jid);
|
||||
Element pubsub = packet.addChild("pubsub", "http://jabber.org/protocol/pubsub#owner");
|
||||
Element configure = pubsub.addChild("configure").setAttribute("node", node);
|
||||
|
@ -630,43 +630,43 @@ public class IqGenerator extends AbstractGenerator {
|
|||
return packet;
|
||||
}
|
||||
|
||||
public IqPacket queryDiscoItems(Jid jid) {
|
||||
IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
|
||||
public Iq queryDiscoItems(final Jid jid) {
|
||||
final Iq packet = new Iq(Iq.Type.GET);
|
||||
packet.setTo(jid);
|
||||
packet.query(Namespace.DISCO_ITEMS);
|
||||
return packet;
|
||||
}
|
||||
|
||||
public IqPacket queryDiscoItems(Jid jid, String node) {
|
||||
IqPacket packet = queryDiscoItems(jid);
|
||||
final Element query = packet.query(Namespace.DISCO_ITEMS);
|
||||
public Iq queryDiscoItems(Jid jid, String node) {
|
||||
final var packet = queryDiscoItems(jid);
|
||||
final var query = packet.query(Namespace.DISCO_ITEMS);
|
||||
query.setAttribute("node", node);
|
||||
return packet;
|
||||
}
|
||||
|
||||
public IqPacket queryDiscoInfo(Jid jid) {
|
||||
IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
|
||||
public Iq queryDiscoInfo(final Jid jid) {
|
||||
final Iq packet = new Iq(Iq.Type.GET);
|
||||
packet.setTo(jid);
|
||||
packet.addChild("query",Namespace.DISCO_INFO);
|
||||
return packet;
|
||||
}
|
||||
|
||||
public IqPacket bobResponse(IqPacket request) {
|
||||
public Iq bobResponse(Iq request) {
|
||||
try {
|
||||
String bobCid = request.findChild("data", "urn:xmpp:bob").getAttribute("cid");
|
||||
Cid cid = BobTransfer.cid(bobCid);
|
||||
DownloadableFile f = mXmppConnectionService.getFileForCid(cid);
|
||||
final var bobCid = request.findChild("data", "urn:xmpp:bob").getAttribute("cid");
|
||||
final var cid = BobTransfer.cid(bobCid);
|
||||
final var f = mXmppConnectionService.getFileForCid(cid);
|
||||
if (f == null || !f.canRead()) {
|
||||
throw new IOException("No such file");
|
||||
} else if (f.getSize() > 129000) {
|
||||
final IqPacket response = request.generateResponse(IqPacket.TYPE.ERROR);
|
||||
final Element error = response.addChild("error");
|
||||
final var response = request.generateResponse(Iq.Type.ERROR);
|
||||
final var error = response.addChild("error");
|
||||
error.setAttribute("type", "cancel");
|
||||
error.addChild("policy-violation", "urn:ietf:params:xml:ns:xmpp-stanzas");
|
||||
return response;
|
||||
} else {
|
||||
final IqPacket response = request.generateResponse(IqPacket.TYPE.RESULT);
|
||||
final Element data = response.addChild("data", "urn:xmpp:bob");
|
||||
final var response = request.generateResponse(Iq.Type.RESULT);
|
||||
final var data = response.addChild("data", "urn:xmpp:bob");
|
||||
data.setAttribute("cid", bobCid);
|
||||
data.setAttribute("type", f.getMimeType());
|
||||
ByteArrayOutputStream b64 = new ByteArrayOutputStream((int) f.getSize() * 2);
|
||||
|
@ -678,8 +678,8 @@ public class IqGenerator extends AbstractGenerator {
|
|||
return response;
|
||||
}
|
||||
} catch (final IOException | IllegalStateException e) {
|
||||
final IqPacket response = request.generateResponse(IqPacket.TYPE.ERROR);
|
||||
final Element error = response.addChild("error");
|
||||
final var response = request.generateResponse(Iq.Type.ERROR);
|
||||
final var error = response.addChild("error");
|
||||
error.setAttribute("type", "cancel");
|
||||
error.addChild("item-not-found", "urn:ietf:params:xml:ns:xmpp-stanzas");
|
||||
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.Media;
|
||||
import eu.siacs.conversations.xmpp.jingle.stanzas.Reason;
|
||||
import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
|
||||
|
||||
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";
|
||||
|
@ -33,25 +32,25 @@ public class MessageGenerator extends AbstractGenerator {
|
|||
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();
|
||||
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();
|
||||
if (conversation.getMode() == Conversation.MODE_SINGLE) {
|
||||
packet.setTo(message.getCounterpart());
|
||||
packet.setType(MessagePacket.TYPE_CHAT);
|
||||
packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.CHAT);
|
||||
if (!isWithSelf) {
|
||||
packet.addChild("request", "urn:xmpp:receipts");
|
||||
}
|
||||
} else if (message.isPrivateMessage()) {
|
||||
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("request", "urn:xmpp:receipts");
|
||||
} else {
|
||||
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()) {
|
||||
packet.addChild("markable", "urn:xmpp:chat-markers:0");
|
||||
|
@ -78,7 +77,7 @@ public class MessageGenerator extends AbstractGenerator {
|
|||
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(
|
||||
"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US);
|
||||
mDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
|
||||
|
@ -87,8 +86,8 @@ public class MessageGenerator extends AbstractGenerator {
|
|||
delay.setAttribute("stamp", mDateFormat.format(date));
|
||||
}
|
||||
|
||||
public MessagePacket generateAxolotlChat(Message message, XmppAxolotlMessage axolotlMessage) {
|
||||
MessagePacket packet = preparePacket(message, true);
|
||||
public im.conversations.android.xmpp.model.stanza.Message generateAxolotlChat(Message message, XmppAxolotlMessage axolotlMessage) {
|
||||
im.conversations.android.xmpp.model.stanza.Message packet = preparePacket(message, true);
|
||||
if (axolotlMessage == null) {
|
||||
return null;
|
||||
}
|
||||
|
@ -101,17 +100,18 @@ public class MessageGenerator extends AbstractGenerator {
|
|||
return packet;
|
||||
}
|
||||
|
||||
public MessagePacket generateKeyTransportMessage(Jid to, XmppAxolotlMessage axolotlMessage) {
|
||||
MessagePacket packet = new MessagePacket();
|
||||
packet.setType(MessagePacket.TYPE_CHAT);
|
||||
public im.conversations.android.xmpp.model.stanza.Message generateKeyTransportMessage(Jid to, XmppAxolotlMessage axolotlMessage) {
|
||||
im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message();
|
||||
packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.CHAT);
|
||||
packet.setTo(to);
|
||||
packet.setAxolotlMessage(axolotlMessage.toElement());
|
||||
packet.addChild("store", "urn:xmpp:hints");
|
||||
return packet;
|
||||
}
|
||||
|
||||
public MessagePacket generateChat(Message message) {
|
||||
MessagePacket packet = preparePacket(message, false);
|
||||
public im.conversations.android.xmpp.model.stanza.Message generateChat(Message message) {
|
||||
im.conversations.android.xmpp.model.stanza.Message packet = preparePacket(message, false);
|
||||
String content;
|
||||
if (message.hasFileOnRemoteHost()) {
|
||||
final Message.FileParams fileParams = message.getFileParams();
|
||||
|
||||
|
@ -139,8 +139,8 @@ public class MessageGenerator extends AbstractGenerator {
|
|||
return packet;
|
||||
}
|
||||
|
||||
public MessagePacket generatePgpChat(Message message) {
|
||||
MessagePacket packet = preparePacket(message, true);
|
||||
public im.conversations.android.xmpp.model.stanza.Message generatePgpChat(Message message) {
|
||||
final im.conversations.android.xmpp.model.stanza.Message packet = preparePacket(message, true);
|
||||
if (message.hasFileOnRemoteHost()) {
|
||||
Message.FileParams fileParams = message.getFileParams();
|
||||
final String url = fileParams.url;
|
||||
|
@ -163,10 +163,10 @@ public class MessageGenerator extends AbstractGenerator {
|
|||
return packet;
|
||||
}
|
||||
|
||||
public MessagePacket generateChatState(Conversation conversation) {
|
||||
public im.conversations.android.xmpp.model.stanza.Message generateChatState(Conversation conversation) {
|
||||
final Account account = conversation.getAccount();
|
||||
MessagePacket packet = new MessagePacket();
|
||||
packet.setType(conversation.getMode() == Conversation.MODE_MULTI ? MessagePacket.TYPE_GROUPCHAT : MessagePacket.TYPE_CHAT);
|
||||
final im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message();
|
||||
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.setFrom(account.getJid());
|
||||
packet.addChild(ChatState.toElement(conversation.getOutgoingChatState()));
|
||||
|
@ -175,11 +175,11 @@ public class MessageGenerator extends AbstractGenerator {
|
|||
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 Jid to = message.getCounterpart();
|
||||
final MessagePacket packet = new MessagePacket();
|
||||
packet.setType(groupChat ? MessagePacket.TYPE_GROUPCHAT : MessagePacket.TYPE_CHAT);
|
||||
final im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message();
|
||||
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);
|
||||
final Element displayed = packet.addChild("displayed", "urn:xmpp:chat-markers:0");
|
||||
if (groupChat) {
|
||||
|
@ -197,20 +197,20 @@ public class MessageGenerator extends AbstractGenerator {
|
|||
return packet;
|
||||
}
|
||||
|
||||
public MessagePacket conferenceSubject(Conversation conversation, String subject) {
|
||||
MessagePacket packet = new MessagePacket();
|
||||
packet.setType(MessagePacket.TYPE_GROUPCHAT);
|
||||
public im.conversations.android.xmpp.model.stanza.Message conferenceSubject(Conversation conversation, String subject) {
|
||||
im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message();
|
||||
packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.GROUPCHAT);
|
||||
packet.setTo(conversation.getJid().asBareJid());
|
||||
packet.addChild("subject").setContent(subject);
|
||||
packet.setFrom(conversation.getAccount().getJid().asBareJid());
|
||||
return packet;
|
||||
}
|
||||
|
||||
public MessagePacket requestVoice(Jid jid) {
|
||||
MessagePacket packet = new MessagePacket();
|
||||
packet.setType(MessagePacket.TYPE_NORMAL);
|
||||
public im.conversations.android.xmpp.model.stanza.Message requestVoice(Jid jid) {
|
||||
final var packet = new im.conversations.android.xmpp.model.stanza.Message();
|
||||
packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.NORMAL);
|
||||
packet.setTo(jid.asBareJid());
|
||||
Data form = new Data();
|
||||
final var form = new Data();
|
||||
form.setFormType("http://jabber.org/protocol/muc#request");
|
||||
form.put("muc#role", "participant");
|
||||
form.submit();
|
||||
|
@ -218,9 +218,9 @@ public class MessageGenerator extends AbstractGenerator {
|
|||
return packet;
|
||||
}
|
||||
|
||||
public MessagePacket directInvite(final Conversation conversation, final Jid contact) {
|
||||
MessagePacket packet = new MessagePacket();
|
||||
packet.setType(MessagePacket.TYPE_NORMAL);
|
||||
public im.conversations.android.xmpp.model.stanza.Message directInvite(final Conversation conversation, final Jid contact) {
|
||||
im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message();
|
||||
packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.NORMAL);
|
||||
packet.setTo(contact);
|
||||
packet.setFrom(conversation.getAccount().getJid());
|
||||
Element x = packet.addChild("x", "jabber:x:conference");
|
||||
|
@ -236,8 +236,8 @@ public class MessageGenerator extends AbstractGenerator {
|
|||
return packet;
|
||||
}
|
||||
|
||||
public MessagePacket invite(final Conversation conversation, final Jid contact) {
|
||||
final MessagePacket packet = new MessagePacket();
|
||||
public im.conversations.android.xmpp.model.stanza.Message invite(final Conversation conversation, final Jid contact) {
|
||||
final var packet = new im.conversations.android.xmpp.model.stanza.Message();
|
||||
packet.setTo(conversation.getJid().asBareJid());
|
||||
packet.setFrom(conversation.getAccount().getJid());
|
||||
Element x = new Element("x");
|
||||
|
@ -249,8 +249,9 @@ public class MessageGenerator extends AbstractGenerator {
|
|||
return packet;
|
||||
}
|
||||
|
||||
public MessagePacket received(Account account, final Jid from, final String id, ArrayList<String> namespaces, int type) {
|
||||
final MessagePacket receivedPacket = new MessagePacket();
|
||||
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 var receivedPacket =
|
||||
new im.conversations.android.xmpp.model.stanza.Message();
|
||||
receivedPacket.setType(type);
|
||||
receivedPacket.setTo(from);
|
||||
receivedPacket.setFrom(account.getJid());
|
||||
|
@ -261,8 +262,8 @@ public class MessageGenerator extends AbstractGenerator {
|
|||
return receivedPacket;
|
||||
}
|
||||
|
||||
public MessagePacket received(Account account, Jid to, String id) {
|
||||
MessagePacket packet = new MessagePacket();
|
||||
public im.conversations.android.xmpp.model.stanza.Message received(Account account, Jid to, String id) {
|
||||
im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message();
|
||||
packet.setFrom(account.getJid());
|
||||
packet.setTo(to);
|
||||
packet.addChild("received", "urn:xmpp:receipts").setAttribute("id", id);
|
||||
|
@ -270,10 +271,10 @@ public class MessageGenerator extends AbstractGenerator {
|
|||
return packet;
|
||||
}
|
||||
|
||||
public MessagePacket sessionFinish(
|
||||
public im.conversations.android.xmpp.model.stanza.Message sessionFinish(
|
||||
final Jid with, final String sessionId, final Reason reason) {
|
||||
final MessagePacket packet = new MessagePacket();
|
||||
packet.setType(MessagePacket.TYPE_CHAT);
|
||||
final im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message();
|
||||
packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.CHAT);
|
||||
packet.setTo(with);
|
||||
final Element finish = packet.addChild("finish", Namespace.JINGLE_MESSAGE);
|
||||
finish.setAttribute("id", sessionId);
|
||||
|
@ -283,9 +284,9 @@ public class MessageGenerator extends AbstractGenerator {
|
|||
return packet;
|
||||
}
|
||||
|
||||
public MessagePacket sessionProposal(final JingleConnectionManager.RtpSessionProposal proposal) {
|
||||
final MessagePacket packet = new MessagePacket();
|
||||
packet.setType(MessagePacket.TYPE_CHAT); //we want to carbon copy those
|
||||
public im.conversations.android.xmpp.model.stanza.Message sessionProposal(final JingleConnectionManager.RtpSessionProposal proposal) {
|
||||
final im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message();
|
||||
packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.CHAT); //we want to carbon copy those
|
||||
packet.setTo(proposal.with);
|
||||
packet.setId(JingleRtpConnection.JINGLE_MESSAGE_PROPOSE_ID_PREFIX + proposal.sessionId);
|
||||
final Element propose = packet.addChild("propose", Namespace.JINGLE_MESSAGE);
|
||||
|
@ -298,9 +299,9 @@ public class MessageGenerator extends AbstractGenerator {
|
|||
return packet;
|
||||
}
|
||||
|
||||
public MessagePacket sessionRetract(final JingleConnectionManager.RtpSessionProposal proposal) {
|
||||
final MessagePacket packet = new MessagePacket();
|
||||
packet.setType(MessagePacket.TYPE_CHAT); //we want to carbon copy those
|
||||
public im.conversations.android.xmpp.model.stanza.Message sessionRetract(final JingleConnectionManager.RtpSessionProposal proposal) {
|
||||
final im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message();
|
||||
packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.CHAT); //we want to carbon copy those
|
||||
packet.setTo(proposal.with);
|
||||
final Element propose = packet.addChild("retract", Namespace.JINGLE_MESSAGE);
|
||||
propose.setAttribute("id", proposal.sessionId);
|
||||
|
@ -309,9 +310,9 @@ public class MessageGenerator extends AbstractGenerator {
|
|||
return packet;
|
||||
}
|
||||
|
||||
public MessagePacket sessionReject(final Jid with, final String sessionId) {
|
||||
final MessagePacket packet = new MessagePacket();
|
||||
packet.setType(MessagePacket.TYPE_CHAT); //we want to carbon copy those
|
||||
public im.conversations.android.xmpp.model.stanza.Message sessionReject(final Jid with, final String sessionId) {
|
||||
final im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message();
|
||||
packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.CHAT); //we want to carbon copy those
|
||||
packet.setTo(with);
|
||||
final Element propose = packet.addChild("reject", Namespace.JINGLE_MESSAGE);
|
||||
propose.setAttribute("id", sessionId);
|
||||
|
|
|
@ -9,7 +9,6 @@ import eu.siacs.conversations.entities.Presence;
|
|||
import eu.siacs.conversations.services.XmppConnectionService;
|
||||
import eu.siacs.conversations.xml.Element;
|
||||
import eu.siacs.conversations.xml.Namespace;
|
||||
import eu.siacs.conversations.xmpp.stanzas.PresencePacket;
|
||||
|
||||
public class PresenceGenerator extends AbstractGenerator {
|
||||
|
||||
|
@ -17,20 +16,20 @@ public class PresenceGenerator extends AbstractGenerator {
|
|||
super(service);
|
||||
}
|
||||
|
||||
private PresencePacket subscription(String type, Contact contact) {
|
||||
PresencePacket packet = new PresencePacket();
|
||||
private im.conversations.android.xmpp.model.stanza.Presence subscription(String type, Contact contact) {
|
||||
im.conversations.android.xmpp.model.stanza.Presence packet = new im.conversations.android.xmpp.model.stanza.Presence();
|
||||
packet.setAttribute("type", type);
|
||||
packet.setTo(contact.getJid());
|
||||
packet.setFrom(contact.getAccount().getJid().asBareJid());
|
||||
return packet;
|
||||
}
|
||||
|
||||
public PresencePacket requestPresenceUpdatesFrom(final Contact contact) {
|
||||
public im.conversations.android.xmpp.model.stanza.Presence requestPresenceUpdatesFrom(final Contact contact) {
|
||||
return requestPresenceUpdatesFrom(contact, null);
|
||||
}
|
||||
|
||||
public PresencePacket requestPresenceUpdatesFrom(final Contact contact, final String preAuth) {
|
||||
PresencePacket packet = subscription("subscribe", contact);
|
||||
public im.conversations.android.xmpp.model.stanza.Presence requestPresenceUpdatesFrom(final Contact contact, final String preAuth) {
|
||||
im.conversations.android.xmpp.model.stanza.Presence packet = subscription("subscribe", contact);
|
||||
String displayName = contact.getAccount().getDisplayName();
|
||||
if (!TextUtils.isEmpty(displayName)) {
|
||||
packet.addChild("nick", Namespace.NICK).setContent(displayName);
|
||||
|
@ -41,24 +40,24 @@ public class PresenceGenerator extends AbstractGenerator {
|
|||
return packet;
|
||||
}
|
||||
|
||||
public PresencePacket stopPresenceUpdatesFrom(Contact contact) {
|
||||
public im.conversations.android.xmpp.model.stanza.Presence stopPresenceUpdatesFrom(Contact 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);
|
||||
}
|
||||
|
||||
public PresencePacket sendPresenceUpdatesTo(Contact contact) {
|
||||
public im.conversations.android.xmpp.model.stanza.Presence sendPresenceUpdatesTo(Contact 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);
|
||||
}
|
||||
|
||||
public PresencePacket selfPresence(final Account account, final Presence.Status status, final boolean personal, final String nickname) {
|
||||
final PresencePacket packet = new PresencePacket();
|
||||
public im.conversations.android.xmpp.model.stanza.Presence selfPresence(final Account account, final Presence.Status status, final boolean personal, final String nickname) {
|
||||
final im.conversations.android.xmpp.model.stanza.Presence packet = new im.conversations.android.xmpp.model.stanza.Presence();
|
||||
if (personal) {
|
||||
final String sig = account.getPgpSignature();
|
||||
final String message = account.getPresenceStatusMessage();
|
||||
|
@ -87,16 +86,16 @@ public class PresenceGenerator extends AbstractGenerator {
|
|||
return packet;
|
||||
}
|
||||
|
||||
public PresencePacket leave(final MucOptions mucOptions) {
|
||||
PresencePacket presencePacket = new PresencePacket();
|
||||
presencePacket.setTo(mucOptions.getSelf().getFullJid());
|
||||
presencePacket.setFrom(mucOptions.getAccount().getJid());
|
||||
presencePacket.setAttribute("type", "unavailable");
|
||||
return presencePacket;
|
||||
public im.conversations.android.xmpp.model.stanza.Presence leave(final MucOptions mucOptions) {
|
||||
im.conversations.android.xmpp.model.stanza.Presence presence = new im.conversations.android.xmpp.model.stanza.Presence();
|
||||
presence.setTo(mucOptions.getSelf().getFullJid());
|
||||
presence.setFrom(mucOptions.getAccount().getJid());
|
||||
presence.setAttribute("type", "unavailable");
|
||||
return presence;
|
||||
}
|
||||
|
||||
public PresencePacket sendOfflinePresence(Account account) {
|
||||
PresencePacket packet = new PresencePacket();
|
||||
public im.conversations.android.xmpp.model.stanza.Presence sendOfflinePresence(Account account) {
|
||||
im.conversations.android.xmpp.model.stanza.Presence packet = new im.conversations.android.xmpp.model.stanza.Presence();
|
||||
packet.setFrom(account.getJid());
|
||||
packet.setAttribute("type", "unavailable");
|
||||
return packet;
|
||||
|
|
|
@ -2,11 +2,27 @@ package eu.siacs.conversations.http;
|
|||
|
||||
import static eu.siacs.conversations.utils.Random.SECURE_RANDOM;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
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 java.io.IOException;
|
||||
|
@ -16,7 +32,9 @@ import java.net.InetSocketAddress;
|
|||
import java.net.Proxy;
|
||||
import java.net.UnknownHostException;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
|
@ -26,19 +44,6 @@ import java.util.concurrent.TimeUnit;
|
|||
import javax.net.ssl.SSLSocketFactory;
|
||||
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 {
|
||||
|
||||
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 OkHttpClient OK_HTTP_CLIENT;
|
||||
private static final OkHttpClient OK_HTTP_CLIENT;
|
||||
|
||||
static {
|
||||
OK_HTTP_CLIENT = new OkHttpClient.Builder()
|
||||
|
@ -209,4 +214,27 @@ public class HttpConnectionManager extends AbstractConnectionManager {
|
|||
|
||||
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.xmpp.IqResponseException;
|
||||
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.HttpUrl;
|
||||
|
||||
|
@ -67,9 +67,9 @@ public class SlotRequester {
|
|||
|
||||
private ListenableFuture<Slot> requestHttpUploadLegacy(Account account, Jid host, DownloadableFile file, String mime) {
|
||||
final SettableFuture<Slot> future = SettableFuture.create();
|
||||
final IqPacket request = service.getIqGenerator().requestHttpUploadLegacySlot(host, file, mime);
|
||||
service.sendIqPacket(account, request, (a, packet) -> {
|
||||
if (packet.getType() == IqPacket.TYPE.RESULT) {
|
||||
final Iq request = service.getIqGenerator().requestHttpUploadLegacySlot(host, file, mime);
|
||||
service.sendIqPacket(account, request, (packet) -> {
|
||||
if (packet.getType() == Iq.Type.RESULT) {
|
||||
final Element slotElement = packet.findChild("slot", Namespace.HTTP_UPLOAD_LEGACY);
|
||||
if (slotElement != null) {
|
||||
try {
|
||||
|
@ -97,9 +97,9 @@ public class SlotRequester {
|
|||
|
||||
private ListenableFuture<Slot> requestHttpUpload(Account account, Jid host, DownloadableFile file, String fname, String mime) {
|
||||
final SettableFuture<Slot> future = SettableFuture.create();
|
||||
final IqPacket request = service.getIqGenerator().requestHttpUploadSlot(host, file, fname, mime);
|
||||
service.sendIqPacket(account, request, (a, packet) -> {
|
||||
if (packet.getType() == IqPacket.TYPE.RESULT) {
|
||||
final Iq request = service.getIqGenerator().requestHttpUploadSlot(host, file, fname, mime);
|
||||
service.sendIqPacket(account, request, (packet) -> {
|
||||
if (packet.getType() == Iq.Type.RESULT) {
|
||||
final Element slotElement = packet.findChild("slot", Namespace.HTTP_UPLOAD);
|
||||
if (slotElement != null) {
|
||||
try {
|
||||
|
|
|
@ -18,14 +18,16 @@ import eu.siacs.conversations.services.XmppConnectionService;
|
|||
import eu.siacs.conversations.xml.Element;
|
||||
import eu.siacs.conversations.xmpp.InvalidJid;
|
||||
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 {
|
||||
|
||||
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.account = account;
|
||||
}
|
||||
|
||||
public static Long parseTimestamp(Element element, Long d) {
|
||||
|
@ -36,8 +38,8 @@ public abstract class AbstractParser {
|
|||
long min = Long.MAX_VALUE;
|
||||
boolean returnDefault = true;
|
||||
final Jid to;
|
||||
if (ignoreCsiAndSm && element instanceof AbstractStanza) {
|
||||
to = ((AbstractStanza) element).getTo();
|
||||
if (ignoreCsiAndSm && element instanceof Stanza stanza) {
|
||||
to = stanza.getTo();
|
||||
} else {
|
||||
to = null;
|
||||
}
|
||||
|
@ -125,7 +127,7 @@ public abstract class AbstractParser {
|
|||
contact.setLastResource(from.isBareJid() ? "" : from.getResource());
|
||||
}
|
||||
|
||||
protected String avatarData(Element items) {
|
||||
protected static String avatarData(Element items) {
|
||||
Element item = items.findChild("item");
|
||||
if (item == null) {
|
||||
return null;
|
||||
|
|
|
@ -26,6 +26,7 @@ import java.util.HashSet;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import eu.siacs.conversations.Config;
|
||||
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.xmpp.InvalidJid;
|
||||
import eu.siacs.conversations.xmpp.Jid;
|
||||
import eu.siacs.conversations.xmpp.OnIqPacketReceived;
|
||||
import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
|
||||
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) {
|
||||
super(service);
|
||||
public IqParser(final XmppConnectionService service, final Account account) {
|
||||
super(service, account);
|
||||
}
|
||||
|
||||
public static List<Jid> items(IqPacket packet) {
|
||||
public static List<Jid> items(final Iq packet) {
|
||||
ArrayList<Jid> items = new ArrayList<>();
|
||||
final Element query = packet.findChild("query", Namespace.DISCO_ITEMS);
|
||||
if (query == null) {
|
||||
|
@ -66,7 +66,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
|
|||
return items;
|
||||
}
|
||||
|
||||
public static Room parseRoom(IqPacket packet) {
|
||||
public static Room parseRoom(Iq packet) {
|
||||
final Element query = packet.findChild("query", Namespace.DISCO_INFO);
|
||||
if (query == null) {
|
||||
return null;
|
||||
|
@ -144,7 +144,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
|
|||
mXmppConnectionService.syncRoster(account);
|
||||
}
|
||||
|
||||
public String avatarData(final IqPacket packet) {
|
||||
public static String avatarData(final Iq packet) {
|
||||
final Element pubsub = packet.findChild("pubsub", Namespace.PUBSUB);
|
||||
if (pubsub == null) {
|
||||
return null;
|
||||
|
@ -153,10 +153,10 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
|
|||
if (items == 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);
|
||||
if (pubsub == null) {
|
||||
return null;
|
||||
|
@ -169,7 +169,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
|
|||
}
|
||||
|
||||
@NonNull
|
||||
public Set<Integer> deviceIds(final Element item) {
|
||||
public static Set<Integer> deviceIds(final Element item) {
|
||||
Set<Integer> deviceIds = new HashSet<>();
|
||||
if (item != null) {
|
||||
final Element list = item.findChild("list");
|
||||
|
@ -190,7 +190,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
|
|||
return deviceIds;
|
||||
}
|
||||
|
||||
private Integer signedPreKeyId(final Element bundle) {
|
||||
private static Integer signedPreKeyId(final Element bundle) {
|
||||
final Element signedPreKeyPublic = bundle.findChild("signedPreKeyPublic");
|
||||
if (signedPreKeyPublic == 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;
|
||||
final String signedPreKeyPublic = bundle.findChildContent("signedPreKeyPublic");
|
||||
if (signedPreKeyPublic == null) {
|
||||
|
@ -216,7 +216,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
|
|||
return publicKey;
|
||||
}
|
||||
|
||||
private byte[] signedPreKeySignature(final Element bundle) {
|
||||
private static byte[] signedPreKeySignature(final Element bundle) {
|
||||
final String signedPreKeySignature = bundle.findChildContent("signedPreKeySignature");
|
||||
if (signedPreKeySignature == 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");
|
||||
if (identityKey == 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<>();
|
||||
Element item = getItem(packet);
|
||||
if (item == null) {
|
||||
|
@ -285,7 +285,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
|
|||
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 verification = item != null ? item.findChild("verification", AxolotlService.PEP_PREFIX) : 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);
|
||||
if (bundleItem == null) {
|
||||
return null;
|
||||
|
@ -337,7 +337,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
|
|||
signedPreKeyId, signedPreKeyPublic, signedPreKeySignature, identityKey);
|
||||
}
|
||||
|
||||
public List<PreKeyBundle> preKeys(final IqPacket preKeys) {
|
||||
public static List<PreKeyBundle> preKeys(final Iq preKeys) {
|
||||
List<PreKeyBundle> bundles = new ArrayList<>();
|
||||
Map<Integer, ECPublicKey> preKeyPublics = preKeyPublics(preKeys);
|
||||
if (preKeyPublics != null) {
|
||||
|
@ -352,15 +352,15 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onIqPacketReceived(final Account account, final IqPacket packet) {
|
||||
final boolean isGet = packet.getType() == IqPacket.TYPE.GET;
|
||||
if (packet.getType() == IqPacket.TYPE.ERROR || packet.getType() == IqPacket.TYPE.TIMEOUT) {
|
||||
public void accept(final Iq packet) {
|
||||
final boolean isGet = packet.getType() == Iq.Type.GET;
|
||||
if (packet.getType() == Iq.Type.ERROR || packet.getType() == Iq.Type.TIMEOUT) {
|
||||
return;
|
||||
}
|
||||
if (packet.hasChild("query", Namespace.ROSTER) && packet.fromServer(account)) {
|
||||
final Element query = packet.findChild("query");
|
||||
// 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();
|
||||
}
|
||||
this.rosterItems(account, query);
|
||||
|
@ -374,7 +374,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
|
|||
(block != null ? block.getChildren() : null);
|
||||
// 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.
|
||||
if (packet.getType() == IqPacket.TYPE.RESULT) {
|
||||
if (packet.getType() == Iq.Type.RESULT) {
|
||||
account.clearBlocklist();
|
||||
account.getXmppConnection().getFeatures().setBlockListRequested(true);
|
||||
}
|
||||
|
@ -390,7 +390,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
|
|||
}
|
||||
}
|
||||
account.getBlocklist().addAll(jids);
|
||||
if (packet.getType() == IqPacket.TYPE.SET) {
|
||||
if (packet.getType() == Iq.Type.SET) {
|
||||
boolean removed = false;
|
||||
for (Jid jid : jids) {
|
||||
removed |= mXmppConnectionService.removeBlockedConversations(account, jid);
|
||||
|
@ -402,15 +402,15 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
|
|||
}
|
||||
// Update the UI
|
||||
mXmppConnectionService.updateBlocklistUi(OnUpdateBlocklist.Status.BLOCKED);
|
||||
if (packet.getType() == IqPacket.TYPE.SET) {
|
||||
final IqPacket response = packet.generateResponse(IqPacket.TYPE.RESULT);
|
||||
if (packet.getType() == Iq.Type.SET) {
|
||||
final Iq response = packet.generateResponse(Iq.Type.RESULT);
|
||||
mXmppConnectionService.sendIqPacket(account, response, null);
|
||||
}
|
||||
} 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");
|
||||
final Collection<Element> items = packet.findChild("unblock", Namespace.BLOCKING).getChildren();
|
||||
if (items.size() == 0) {
|
||||
if (items.isEmpty()) {
|
||||
// No children to unblock == unblock all
|
||||
account.getBlocklist().clear();
|
||||
} else {
|
||||
|
@ -426,7 +426,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
|
|||
account.getBlocklist().removeAll(jids);
|
||||
}
|
||||
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);
|
||||
} else if (packet.hasChild("open", "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()
|
||||
.deliverIbbPacket(account, packet);
|
||||
} 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);
|
||||
} 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);
|
||||
} 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);
|
||||
} else if (packet.hasChild("time", "urn:xmpp:time") && isGet) {
|
||||
final IqPacket response;
|
||||
final Iq response;
|
||||
if (mXmppConnectionService.useTorToConnect() || account.isOnion()) {
|
||||
response = packet.generateResponse(IqPacket.TYPE.ERROR);
|
||||
response = packet.generateResponse(Iq.Type.ERROR);
|
||||
final Element error = response.addChild("error");
|
||||
error.setAttribute("type", "cancel");
|
||||
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);
|
||||
}
|
||||
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 Element push = packet.findChild("push", Namespace.UNIFIED_PUSH);
|
||||
final boolean success =
|
||||
push != null
|
||||
&& mXmppConnectionService.processUnifiedPushMessage(
|
||||
account, transport, push);
|
||||
final IqPacket response;
|
||||
final Iq response;
|
||||
if (success) {
|
||||
response = packet.generateResponse(IqPacket.TYPE.RESULT);
|
||||
response = packet.generateResponse(Iq.Type.RESULT);
|
||||
} else {
|
||||
response = packet.generateResponse(IqPacket.TYPE.ERROR);
|
||||
response = packet.generateResponse(Iq.Type.ERROR);
|
||||
final Element error = response.addChild("error");
|
||||
error.setAttribute("type", "cancel");
|
||||
error.setAttribute("code", "404");
|
||||
|
@ -476,8 +476,8 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
|
|||
final Conversation conversation = mXmppConnectionService.find(account, packet.getFrom());
|
||||
if (packet.hasChild("data", "urn:xmpp:bob") && isGet && (conversation == null ? contact != null && contact.canInferPresence() : conversation.canInferPresence())) {
|
||||
mXmppConnectionService.sendIqPacket(account, mXmppConnectionService.getIqGenerator().bobResponse(packet), null);
|
||||
} else if (packet.getType() == IqPacket.TYPE.GET || packet.getType() == IqPacket.TYPE.SET) {
|
||||
final IqPacket response = packet.generateResponse(IqPacket.TYPE.ERROR);
|
||||
} else if (packet.getType() == Iq.Type.GET || packet.getType() == Iq.Type.SET) {
|
||||
final var response = packet.generateResponse(Iq.Type.ERROR);
|
||||
final Element error = response.addChild("error");
|
||||
error.setAttribute("type", "cancel");
|
||||
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.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
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.JingleRtpConnection;
|
||||
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 List<String> JINGLE_MESSAGE_ELEMENT_NAMES =
|
||||
Arrays.asList("accept", "propose", "proceed", "reject", "retract", "ringing", "finish");
|
||||
|
||||
public MessageParser(XmppConnectionService service) {
|
||||
super(service);
|
||||
public MessageParser(final XmppConnectionService service, final Account account) {
|
||||
super(service, account);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
if (state != null && c != null) {
|
||||
final Account account = c.getAccount();
|
||||
|
@ -251,7 +255,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
|
|||
}
|
||||
} else if (AxolotlService.PEP_DEVICE_LIST.equals(node)) {
|
||||
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... ");
|
||||
final AxolotlService axolotlService = account.getAxolotlService();
|
||||
axolotlService.registerDevices(from, deviceIds);
|
||||
|
@ -358,10 +362,10 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
|
|||
mXmppConnectionService.updateAccountUi();
|
||||
}
|
||||
|
||||
private boolean handleErrorMessage(final Account account, final MessagePacket packet) {
|
||||
if (packet.getType() == MessagePacket.TYPE_ERROR) {
|
||||
private boolean handleErrorMessage(final Account account, final im.conversations.android.xmpp.model.stanza.Message packet) {
|
||||
if (packet.getType() == im.conversations.android.xmpp.model.stanza.Message.Type.ERROR) {
|
||||
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) {
|
||||
return handleErrorMessage(account, forwarded.first);
|
||||
}
|
||||
|
@ -404,11 +408,11 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onMessagePacketReceived(Account account, MessagePacket original) {
|
||||
public void accept(final im.conversations.android.xmpp.model.stanza.Message original) {
|
||||
if (handleErrorMessage(account, original)) {
|
||||
return;
|
||||
}
|
||||
final MessagePacket packet;
|
||||
final im.conversations.android.xmpp.model.stanza.Message packet;
|
||||
Long timestamp = null;
|
||||
boolean isCarbon = false;
|
||||
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 boolean offlineMessagesRetrieved = account.getXmppConnection().isOfflineMessagesRetrieved();
|
||||
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) {
|
||||
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 + ")");
|
||||
return;
|
||||
} else if (original.fromServer(account)) {
|
||||
Pair<MessagePacket, Long> f;
|
||||
f = original.getForwardedMessagePacket("received", Namespace.CARBONS);
|
||||
f = f == null ? original.getForwardedMessagePacket("sent", Namespace.CARBONS) : f;
|
||||
Pair<im.conversations.android.xmpp.model.stanza.Message, Long> f;
|
||||
f = getForwardedMessagePacket(original, Received.class);
|
||||
f = f == null ? getForwardedMessagePacket(original, Sent.class) : f;
|
||||
packet = f != null ? f.first : original;
|
||||
if (handleErrorMessage(account, packet)) {
|
||||
return;
|
||||
|
@ -514,7 +518,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
|
|||
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) {
|
||||
Log.e(Config.LOGTAG, account.getJid().asBareJid() + ": received groupchat (" + from + ") message on regular MAM request. skipping");
|
||||
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) {
|
||||
final Conversation conversation = mXmppConnectionService.find(account, counterpart.asBareJid());
|
||||
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 request = packet.hasChild("request", "urn:xmpp:receipts");
|
||||
if (query == null) {
|
||||
|
@ -1324,7 +1356,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
|
|||
receiptsNamespaces.add("urn:xmpp:receipts");
|
||||
}
|
||||
if (receiptsNamespaces.size() > 0) {
|
||||
final MessagePacket receipt = mXmppConnectionService.getMessageGenerator().received(account,
|
||||
final var receipt = mXmppConnectionService.getMessageGenerator().received(account,
|
||||
packet.getFrom(),
|
||||
remoteMsgId,
|
||||
receiptsNamespaces,
|
||||
|
|
|
@ -19,22 +19,21 @@ import eu.siacs.conversations.xml.Element;
|
|||
import eu.siacs.conversations.xml.Namespace;
|
||||
import eu.siacs.conversations.xmpp.InvalidJid;
|
||||
import eu.siacs.conversations.xmpp.Jid;
|
||||
import eu.siacs.conversations.xmpp.OnPresencePacketReceived;
|
||||
import eu.siacs.conversations.xmpp.pep.Avatar;
|
||||
import eu.siacs.conversations.xmpp.stanzas.PresencePacket;
|
||||
|
||||
import org.openintents.openpgp.util.OpenPgpUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
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) {
|
||||
super(service);
|
||||
public PresenceParser(final XmppConnectionService service, final Account account) {
|
||||
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 =
|
||||
packet.getFrom() == 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 MucOptions mucOptions = conversation.getMucOptions();
|
||||
final Jid jid = conversation.getAccount().getJid();
|
||||
|
@ -300,7 +297,7 @@ public class PresenceParser extends AbstractParser implements OnPresencePacketRe
|
|||
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 Jid from = packet.getFrom();
|
||||
if (from == null || from.equals(account.getJid())) {
|
||||
|
@ -434,7 +431,7 @@ public class PresenceParser extends AbstractParser implements OnPresencePacketRe
|
|||
}
|
||||
|
||||
@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)) {
|
||||
this.parseConferencePresence(packet, account);
|
||||
} 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 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.ByteArrayOutputStream;
|
||||
import java.io.Closeable;
|
||||
|
@ -85,22 +100,6 @@ import org.tomlj.TomlTable;
|
|||
|
||||
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 {
|
||||
|
||||
private static final Object THUMBNAIL_LOCK = new Object();
|
||||
|
@ -784,16 +783,16 @@ public class FileBackend {
|
|||
}
|
||||
|
||||
private void copyFileToPrivateStorage(File file, Uri uri) throws FileCopyException {
|
||||
Log.d(
|
||||
Config.LOGTAG,
|
||||
"copy file (" + uri.toString() + ") to private storage " + file.getAbsolutePath());
|
||||
file.getParentFile().mkdirs();
|
||||
final var parentDirectory = file.getParentFile();
|
||||
if (parentDirectory != null && parentDirectory.mkdirs()) {
|
||||
Log.d(Config.LOGTAG,"created directory "+parentDirectory.getAbsolutePath());
|
||||
}
|
||||
try {
|
||||
if (!file.createNewFile() && file.length() > 0) {
|
||||
if (file.canRead() && file.getName().startsWith("zb2")) return; // We have this content already
|
||||
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);
|
||||
}
|
||||
try (final OutputStream os = new FileOutputStream(file);
|
||||
|
@ -803,12 +802,12 @@ public class FileBackend {
|
|||
}
|
||||
try {
|
||||
ByteStreams.copy(is, os);
|
||||
} catch (IOException e) {
|
||||
} catch (final IOException e) {
|
||||
throw new FileWriterException(file);
|
||||
}
|
||||
try {
|
||||
os.flush();
|
||||
} catch (IOException e) {
|
||||
} catch (final IOException e) {
|
||||
throw new FileWriterException(file);
|
||||
}
|
||||
} catch (final FileNotFoundException e) {
|
||||
|
@ -817,7 +816,7 @@ public class FileBackend {
|
|||
} catch (final FileWriterException e) {
|
||||
cleanup(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);
|
||||
throw new FileCopyException(R.string.error_security_exception);
|
||||
} catch (final IOException e) {
|
||||
|
@ -828,7 +827,7 @@ public class FileBackend {
|
|||
|
||||
public void copyFileToPrivateStorage(Message message, Uri uri, String type)
|
||||
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 + ")");
|
||||
String extension = MimeUtils.guessExtensionFromMimeType(mime);
|
||||
if (extension == null) {
|
||||
|
@ -1168,9 +1167,9 @@ public class FileBackend {
|
|||
}
|
||||
|
||||
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()) {
|
||||
for (Element thumb : thumbs) {
|
||||
for (final var thumb : thumbs) {
|
||||
final var uriS = thumb.getAttribute("uri");
|
||||
if (uriS == null) continue;
|
||||
Uri uri = Uri.parse(uriS);
|
||||
|
@ -1240,9 +1239,9 @@ public class FileBackend {
|
|||
|
||||
if ((thumbnail == null) && (!cacheOnly)) {
|
||||
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()) {
|
||||
for (Element thumb : thumbs) {
|
||||
for (final var thumb : thumbs) {
|
||||
final var uriS = thumb.getAttribute("uri");
|
||||
if (uriS == null) continue;
|
||||
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
|
||||
* 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;
|
||||
private static final int DEFAULT_MEDIA_PLAYER_VOLUME = 90;
|
||||
|
@ -393,9 +397,7 @@ public class CallIntegration extends Connection {
|
|||
|
||||
public void success() {
|
||||
Log.d(Config.LOGTAG, "CallIntegration.success()");
|
||||
final var toneGenerator =
|
||||
new ToneGenerator(AudioManager.STREAM_VOICE_CALL, DEFAULT_TONE_VOLUME);
|
||||
toneGenerator.startTone(ToneGenerator.TONE_CDMA_CALLDROP_LITE, 375);
|
||||
startTone(DEFAULT_TONE_VOLUME, ToneGenerator.TONE_CDMA_CALLDROP_LITE, 375);
|
||||
this.destroyWithDelay(new DisconnectCause(DisconnectCause.LOCAL, null), 375);
|
||||
}
|
||||
|
||||
|
@ -410,9 +412,7 @@ public class CallIntegration extends Connection {
|
|||
|
||||
public void error() {
|
||||
Log.d(Config.LOGTAG, "CallIntegration.error()");
|
||||
final var toneGenerator =
|
||||
new ToneGenerator(AudioManager.STREAM_VOICE_CALL, DEFAULT_TONE_VOLUME);
|
||||
toneGenerator.startTone(ToneGenerator.TONE_CDMA_CALLDROP_LITE, 375);
|
||||
startTone(DEFAULT_TONE_VOLUME, ToneGenerator.TONE_CDMA_CALLDROP_LITE, 375);
|
||||
this.destroyWithDelay(new DisconnectCause(DisconnectCause.ERROR, null), 375);
|
||||
}
|
||||
|
||||
|
@ -429,8 +429,7 @@ public class CallIntegration extends Connection {
|
|||
|
||||
public void busy() {
|
||||
Log.d(Config.LOGTAG, "CallIntegration.busy()");
|
||||
final var toneGenerator = new ToneGenerator(AudioManager.STREAM_VOICE_CALL, 80);
|
||||
toneGenerator.startTone(ToneGenerator.TONE_CDMA_NETWORK_BUSY, 2500);
|
||||
startTone(80, ToneGenerator.TONE_CDMA_NETWORK_BUSY, 2500);
|
||||
this.destroyWithDelay(new DisconnectCause(DisconnectCause.BUSY, null), 2500);
|
||||
}
|
||||
|
||||
|
@ -458,6 +457,17 @@ public class CallIntegration extends Connection {
|
|||
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) {
|
||||
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) {
|
||||
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
|
||||
// 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
|
||||
|
|
|
@ -36,6 +36,7 @@ import eu.siacs.conversations.entities.Contact;
|
|||
import eu.siacs.conversations.ui.RtpSessionActivity;
|
||||
import eu.siacs.conversations.xmpp.Jid;
|
||||
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.Media;
|
||||
import eu.siacs.conversations.xmpp.jingle.RtpEndUserState;
|
||||
|
@ -126,9 +127,17 @@ public class CallIntegrationConnectionService extends ConnectionService {
|
|||
// actually attempted
|
||||
// sendJingleFinishMessage(service, contact, Reason.CONNECTIVITY_ERROR);
|
||||
} else {
|
||||
final var proposal =
|
||||
final JingleConnectionManager.RtpSessionProposal proposal;
|
||||
try {
|
||||
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) {
|
||||
// TODO instead of just null checking try to get the sessionID
|
||||
return Connection.createFailedConnection(
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
package eu.siacs.conversations.services;
|
||||
|
||||
import static eu.siacs.conversations.utils.Random.SECURE_RANDOM;
|
||||
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
@ -12,17 +10,15 @@ import com.google.common.cache.Cache;
|
|||
import com.google.common.cache.CacheBuilder;
|
||||
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.crypto.TrustManagers;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.entities.Room;
|
||||
import eu.siacs.conversations.http.HttpConnectionManager;
|
||||
import eu.siacs.conversations.http.services.MuclumbusService;
|
||||
import eu.siacs.conversations.parser.IqParser;
|
||||
import eu.siacs.conversations.utils.TLSSocketFactory;
|
||||
import eu.siacs.conversations.xmpp.Jid;
|
||||
import eu.siacs.conversations.xmpp.OnIqPacketReceived;
|
||||
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.ResponseBody;
|
||||
|
@ -34,10 +30,6 @@ import retrofit2.Retrofit;
|
|||
import retrofit2.converter.gson.GsonConverterFactory;
|
||||
|
||||
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.Collections;
|
||||
import java.util.HashMap;
|
||||
|
@ -47,9 +39,6 @@ import java.util.concurrent.Executors;
|
|||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
public class ChannelDiscoveryService {
|
||||
|
||||
private final XmppConnectionService service;
|
||||
|
@ -68,25 +57,7 @@ public class ChannelDiscoveryService {
|
|||
this.muclumbusService = null;
|
||||
return;
|
||||
}
|
||||
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(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);
|
||||
}
|
||||
final OkHttpClient.Builder builder = HttpConnectionManager.okHttpClient(service).newBuilder();
|
||||
if (service.useTorToConnect()) {
|
||||
builder.proxy(HttpConnectionManager.getProxy());
|
||||
}
|
||||
|
@ -203,7 +174,7 @@ public class ChannelDiscoveryService {
|
|||
final String query, Map<Jid, Account> mucServices, final OnChannelSearchResultsFound listener) {
|
||||
final Map<Jid, Account> localMucService = mucServices == null ? getLocalMucServices() : mucServices;
|
||||
Log.d(Config.LOGTAG, "checking with " + localMucService.size() + " muc services");
|
||||
if (localMucService.size() == 0) {
|
||||
if (localMucService.isEmpty()) {
|
||||
listener.onChannelSearchResultsFound(Collections.emptyList());
|
||||
return;
|
||||
}
|
||||
|
@ -217,38 +188,37 @@ public class ChannelDiscoveryService {
|
|||
}
|
||||
final AtomicInteger queriesInFlight = new AtomicInteger();
|
||||
final List<Room> rooms = new ArrayList<>();
|
||||
for (Map.Entry<Jid, Account> entry : localMucService.entrySet()) {
|
||||
IqPacket itemsRequest = service.getIqGenerator().queryDiscoItems(entry.getKey());
|
||||
for (final Map.Entry<Jid, Account> entry : localMucService.entrySet()) {
|
||||
Iq itemsRequest = service.getIqGenerator().queryDiscoItems(entry.getKey());
|
||||
queriesInFlight.incrementAndGet();
|
||||
final var account = entry.getValue();
|
||||
service.sendIqPacket(
|
||||
entry.getValue(),
|
||||
account,
|
||||
itemsRequest,
|
||||
(account, itemsResponse) -> {
|
||||
if (itemsResponse.getType() == IqPacket.TYPE.RESULT) {
|
||||
(itemsResponse) -> {
|
||||
if (itemsResponse.getType() == Iq.Type.RESULT) {
|
||||
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
|
||||
IqPacket infoRequest =
|
||||
final Iq infoRequest =
|
||||
service.getIqGenerator().queryDiscoInfo(item);
|
||||
queriesInFlight.incrementAndGet();
|
||||
service.sendIqPacket(
|
||||
account,
|
||||
infoRequest,
|
||||
new OnIqPacketReceived() {
|
||||
@Override
|
||||
public void onIqPacketReceived(
|
||||
Account account, IqPacket infoResponse) {
|
||||
infoResponse -> {
|
||||
if (infoResponse.getType()
|
||||
== IqPacket.TYPE.RESULT) {
|
||||
== Iq.Type.RESULT) {
|
||||
final Room room =
|
||||
IqParser.parseRoom(infoResponse);
|
||||
if (room != null) {
|
||||
rooms.add(room);
|
||||
}
|
||||
}
|
||||
if (queriesInFlight.decrementAndGet() <= 0) {
|
||||
finishDiscoSearch(rooms, query, mucServices, listener);
|
||||
}
|
||||
} else {
|
||||
queriesInFlight.decrementAndGet();
|
||||
}
|
||||
}, 20L);
|
||||
}
|
||||
|
|
|
@ -41,7 +41,6 @@ import android.util.Log;
|
|||
import android.util.SparseArray;
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.util.Consumer;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.base.Joiner;
|
||||
|
@ -86,6 +85,7 @@ import java.util.ArrayList;
|
|||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
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.OnAdvancedStreamFeaturesLoaded;
|
||||
import eu.siacs.conversations.xmpp.mam.MamReference;
|
||||
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
|
||||
import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
|
||||
import im.conversations.android.xmpp.model.stanza.Iq;
|
||||
import im.conversations.android.xmpp.model.stanza.Message;
|
||||
|
||||
public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
|
||||
|
||||
|
@ -81,7 +81,7 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
|
|||
return false;
|
||||
}
|
||||
|
||||
public static Element findResult(MessagePacket packet) {
|
||||
public static Element findResult(Message packet) {
|
||||
for (Version version : values()) {
|
||||
Element result = packet.findChild("result", version.namespace);
|
||||
if (result != null) {
|
||||
|
@ -233,17 +233,17 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
|
|||
throw new IllegalStateException("Attempted to run MAM query for archived conversation");
|
||||
}
|
||||
Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() + ": running mam query " + query.toString());
|
||||
final IqPacket packet = this.mXmppConnectionService.getIqGenerator().queryMessageArchiveManagement(query);
|
||||
this.mXmppConnectionService.sendIqPacket(account, packet, (a, p) -> {
|
||||
final Iq packet = this.mXmppConnectionService.getIqGenerator().queryMessageArchiveManagement(query);
|
||||
this.mXmppConnectionService.sendIqPacket(account, packet, (p) -> {
|
||||
final Element fin = p.findChild("fin", query.version.namespace);
|
||||
if (p.getType() == IqPacket.TYPE.TIMEOUT) {
|
||||
if (p.getType() == Iq.Type.TIMEOUT) {
|
||||
synchronized (this.queries) {
|
||||
this.queries.remove(query);
|
||||
if (query.hasCallback()) {
|
||||
query.callback(false);
|
||||
}
|
||||
}
|
||||
} else if (p.getType() == IqPacket.TYPE.RESULT && fin != null) {
|
||||
} else if (p.getType() == Iq.Type.RESULT && fin != null) {
|
||||
final boolean running;
|
||||
synchronized (this.queries) {
|
||||
running = this.queries.contains(query);
|
||||
|
@ -253,10 +253,10 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
|
|||
} else {
|
||||
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
|
||||
} 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 {
|
||||
finalizeQuery(query, true);
|
||||
} 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) {
|
||||
for (Query query : queries) {
|
||||
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();
|
||||
return true;
|
||||
});
|
||||
this.binding.editMucNameButton.setContentDescription(getString(R.string.edit_name_and_topic));
|
||||
this.binding.editMucNameButton.setOnClickListener(this::onMucEditButtonClicked);
|
||||
this.binding.mucEditTitle.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.mucDisplay.setVisibility(View.VISIBLE);
|
||||
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) {
|
||||
|
@ -669,7 +671,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
|
|||
});
|
||||
this.mUserPreviewAdapter.submitList(MucOptions.sub(users, GridManager.getCurrentColumnCount(binding.users)));
|
||||
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.usersWrapper.setVisibility(users.size() > 0 || mucOptions.canInvite() ? View.VISIBLE : View.GONE);
|
||||
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.OngoingRtpSession;
|
||||
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;
|
||||
|
||||
|
@ -3587,13 +3588,13 @@ public class ConversationFragment extends XmppFragment
|
|||
} else {
|
||||
if (!delayShow) conversation.showViewPager();
|
||||
binding.commandsViewProgressbar.setVisibility(View.VISIBLE);
|
||||
activity.xmppConnectionService.fetchCommands(conversation.getAccount(), commandJid, (a, iq) -> {
|
||||
activity.xmppConnectionService.fetchCommands(conversation.getAccount(), commandJid, (iq) -> {
|
||||
if (activity == null) return;
|
||||
|
||||
activity.runOnUiThread(() -> {
|
||||
binding.commandsViewProgressbar.setVisibility(View.GONE);
|
||||
commandAdapter.clear();
|
||||
if (iq.getType() == IqPacket.TYPE.RESULT) {
|
||||
if (iq.getType() == Iq.Type.RESULT) {
|
||||
for (Element child : iq.query().getChildren()) {
|
||||
if (!"item".equals(child.getName()) || !Namespace.DISCO_ITEMS.equals(child.getNamespace())) continue;
|
||||
commandAdapter.add(new CommandAdapter.Command0050(child));
|
||||
|
|
|
@ -257,7 +257,8 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
|
|||
if (ExceptionHelper.checkForCrash(this)) return;
|
||||
if (offerToSetupDiallerIntegration()) return;
|
||||
if (offerToDownloadStickers()) return;
|
||||
openBatteryOptimizationDialogIfNeeded();
|
||||
if (openBatteryOptimizationDialogIfNeeded()) return;
|
||||
requestNotificationPermissionIfNeeded();
|
||||
xmppConnectionService.rescanStickers();
|
||||
}
|
||||
}
|
||||
|
@ -282,7 +283,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
|
|||
intent.setData(uri);
|
||||
try {
|
||||
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();
|
||||
}
|
||||
});
|
||||
|
@ -375,16 +376,16 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
|
|||
|
||||
private void notifyFragmentOfBackendConnected(@IdRes int id) {
|
||||
final Fragment fragment = getFragmentManager().findFragmentById(id);
|
||||
if (fragment instanceof OnBackendConnected) {
|
||||
((OnBackendConnected) fragment).onBackendConnected();
|
||||
if (fragment instanceof OnBackendConnected callback) {
|
||||
callback.onBackendConnected();
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshFragment(@IdRes int id) {
|
||||
final Fragment fragment = getFragmentManager().findFragmentById(id);
|
||||
if (fragment instanceof XmppFragment) {
|
||||
((XmppFragment) fragment).refresh();
|
||||
if (refreshForNewCaps) ((XmppFragment) fragment).refreshForNewCaps(newCapsJids);
|
||||
if (fragment instanceof XmppFragment xmppFragment) {
|
||||
xmppFragment.refresh();
|
||||
if (refreshForNewCaps) xmppFragment.refreshForNewCaps(newCapsJids);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -47,6 +47,7 @@ import android.view.ViewGroup;
|
|||
import android.widget.AdapterView.AdapterContextMenuInfo;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.databinding.DataBindingUtil;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||
|
@ -96,42 +97,36 @@ public class ConversationsOverviewFragment extends XmppFragment {
|
|||
private FragmentConversationsOverviewBinding binding;
|
||||
private ConversationAdapter conversationsAdapter;
|
||||
private XmppActivity activity;
|
||||
private float mSwipeEscapeVelocity = 0f;
|
||||
private final PendingActionHelper pendingActionHelper = new PendingActionHelper();
|
||||
|
||||
private final ItemTouchHelper.SimpleCallback callback = new ItemTouchHelper.SimpleCallback(0,LEFT|RIGHT) {
|
||||
@Override
|
||||
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
|
||||
//todo maybe we can manually changing the position of the conversation
|
||||
public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getSwipeEscapeVelocity (float defaultValue) {
|
||||
return mSwipeEscapeVelocity;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
|
||||
public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView, @NonNull 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);
|
||||
if (viewHolder instanceof ConversationAdapter.ConversationViewHolder conversationViewHolder) {
|
||||
getDefaultUIUtil().onDraw(c,recyclerView,conversationViewHolder.binding.frame,dX,dY,actionState,isCurrentlyActive);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
|
||||
super.clearView(recyclerView, viewHolder);
|
||||
viewHolder.itemView.setAlpha(1f);
|
||||
public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
|
||||
if (viewHolder instanceof ConversationAdapter.ConversationViewHolder conversationViewHolder) {
|
||||
getDefaultUIUtil().clearView(conversationViewHolder.binding.frame);
|
||||
}
|
||||
}
|
||||
|
||||
@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();
|
||||
int position = viewHolder.getLayoutPosition();
|
||||
try {
|
||||
|
@ -291,7 +286,6 @@ public class ConversationsOverviewFragment extends XmppFragment {
|
|||
|
||||
@Override
|
||||
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.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.Preconditions;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.base.Throwables;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
|
@ -363,10 +364,15 @@ public class RtpSessionActivity extends XmppActivity
|
|||
|
||||
private void acceptContentAdd() {
|
||||
try {
|
||||
requireRtpConnection()
|
||||
.acceptContentAdd(requireRtpConnection().getPendingContentAddition().summary);
|
||||
final ContentAddition pendingContentAddition =
|
||||
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) {
|
||||
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();
|
||||
Log.d(Config.LOGTAG, "initializeWithIntent(" + event + "," + action + ")");
|
||||
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);
|
||||
if (sessionId != null) {
|
||||
if (initializeActivityWithRunningRtpSession(account, with, sessionId)) {
|
||||
|
@ -1089,16 +1100,21 @@ public class RtpSessionActivity extends XmppActivity
|
|||
final CallIntegration.AudioDevice selectedAudioDevice, final int numberOfChoices) {
|
||||
switch (selectedAudioDevice) {
|
||||
case EARPIECE -> {
|
||||
this.binding.inCallActionRight.setImageResource(
|
||||
R.drawable.ic_volume_off_24dp);
|
||||
this.binding.inCallActionRight.setImageResource(R.drawable.ic_volume_off_24dp);
|
||||
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);
|
||||
} else {
|
||||
this.binding.inCallActionRight.setContentDescription(
|
||||
getString(R.string.call_is_using_earpiece));
|
||||
this.binding.inCallActionRight.setOnClickListener(null);
|
||||
this.binding.inCallActionRight.setClickable(false);
|
||||
}
|
||||
}
|
||||
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.setOnClickListener(null);
|
||||
this.binding.inCallActionRight.setClickable(false);
|
||||
|
@ -1106,15 +1122,20 @@ public class RtpSessionActivity extends XmppActivity
|
|||
case SPEAKER_PHONE -> {
|
||||
this.binding.inCallActionRight.setImageResource(R.drawable.ic_volume_up_24dp);
|
||||
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);
|
||||
} else {
|
||||
this.binding.inCallActionRight.setContentDescription(
|
||||
getString(R.string.call_is_using_speaker));
|
||||
this.binding.inCallActionRight.setOnClickListener(null);
|
||||
this.binding.inCallActionRight.setClickable(false);
|
||||
}
|
||||
}
|
||||
case BLUETOOTH -> {
|
||||
this.binding.inCallActionRight.setImageResource(
|
||||
R.drawable.ic_bluetooth_audio_24dp);
|
||||
this.binding.inCallActionRight.setContentDescription(
|
||||
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.setClickable(false);
|
||||
}
|
||||
|
@ -1131,15 +1152,21 @@ public class RtpSessionActivity extends XmppActivity
|
|||
R.drawable.ic_flip_camera_android_24dp);
|
||||
this.binding.inCallActionFarRight.setVisibility(View.VISIBLE);
|
||||
this.binding.inCallActionFarRight.setOnClickListener(this::switchCamera);
|
||||
this.binding.inCallActionFarRight.setContentDescription(
|
||||
getString(R.string.flip_camera));
|
||||
} else {
|
||||
this.binding.inCallActionFarRight.setVisibility(View.GONE);
|
||||
}
|
||||
if (videoEnabled) {
|
||||
this.binding.inCallActionRight.setImageResource(R.drawable.ic_videocam_24dp);
|
||||
this.binding.inCallActionRight.setOnClickListener(this::disableVideo);
|
||||
this.binding.inCallActionRight.setContentDescription(
|
||||
getString(R.string.video_is_enabled_tap_to_disable));
|
||||
} else {
|
||||
this.binding.inCallActionRight.setImageResource(R.drawable.ic_videocam_off_24dp);
|
||||
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) {
|
||||
try {
|
||||
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) {
|
||||
try {
|
||||
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) {
|
||||
|
|
|
@ -39,7 +39,6 @@ import android.widget.AutoCompleteTextView;
|
|||
import android.widget.CheckBox;
|
||||
import android.widget.EditText;
|
||||
import android.widget.ListView;
|
||||
import android.widget.Spinner;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
|
@ -50,7 +49,7 @@ import androidx.annotation.StringRes;
|
|||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.widget.PopupMenu;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.databinding.DataBindingUtil;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
|
@ -67,21 +66,11 @@ import com.google.android.material.bottomnavigation.BottomNavigationView;
|
|||
import com.google.android.material.color.MaterialColors;
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
||||
import com.google.android.material.textfield.TextInputLayout;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.leinardi.android.speeddial.SpeedDialActionItem;
|
||||
import com.leinardi.android.speeddial.SpeedDialView;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import eu.siacs.conversations.BuildConfig;
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.R;
|
||||
|
@ -113,11 +102,29 @@ import eu.siacs.conversations.xmpp.Jid;
|
|||
import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
|
||||
import eu.siacs.conversations.xmpp.XmppConnection;
|
||||
import eu.siacs.conversations.xmpp.forms.Data;
|
||||
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
|
||||
|
||||
public class StartConversationActivity extends XmppActivity implements XmppConnectionService.OnConversationUpdate, OnRosterUpdate, OnUpdateBlocklist, CreatePrivateGroupChatDialog.CreateConferenceDialogListener, JoinConferenceDialog.JoinConferenceDialogListener, SwipeRefreshLayout.OnRefreshListener, CreatePublicChannelDialog.CreatePublicChannelDialogListener {
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
private static final String PREF_KEY_CONTACT_INTEGRATION_CONSENT = "contact_list_integration_consent";
|
||||
public class StartConversationActivity extends XmppActivity
|
||||
implements XmppConnectionService.OnConversationUpdate,
|
||||
OnRosterUpdate,
|
||||
OnUpdateBlocklist,
|
||||
CreatePrivateGroupChatDialog.CreateConferenceDialogListener,
|
||||
JoinConferenceDialog.JoinConferenceDialogListener,
|
||||
SwipeRefreshLayout.OnRefreshListener,
|
||||
CreatePublicChannelDialog.CreatePublicChannelDialogListener {
|
||||
|
||||
private static final String PREF_KEY_CONTACT_INTEGRATION_CONSENT =
|
||||
"contact_list_integration_consent";
|
||||
|
||||
public static final String EXTRA_INVITE_URI = "eu.siacs.conversations.invite_uri";
|
||||
|
||||
|
@ -139,19 +146,24 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
|||
private final AtomicBoolean mOpenedFab = new AtomicBoolean(false);
|
||||
private boolean mHideOfflineContacts = false;
|
||||
private boolean createdByViewIntent = false;
|
||||
private final MenuItem.OnActionExpandListener mOnActionExpandListener = new MenuItem.OnActionExpandListener() {
|
||||
private final MenuItem.OnActionExpandListener mOnActionExpandListener =
|
||||
new MenuItem.OnActionExpandListener() {
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemActionExpand(MenuItem item) {
|
||||
mSearchEditText.post(() -> {
|
||||
public boolean onMenuItemActionExpand(@NonNull final MenuItem item) {
|
||||
mSearchEditText.post(
|
||||
() -> {
|
||||
updateSearchViewHint();
|
||||
mSearchEditText.requestFocus();
|
||||
if (oneShotKeyboardSuppress.compareAndSet(true, false)) {
|
||||
return;
|
||||
}
|
||||
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
InputMethodManager imm =
|
||||
(InputMethodManager)
|
||||
getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
if (imm != null) {
|
||||
imm.showSoftInput(mSearchEditText, InputMethodManager.SHOW_IMPLICIT);
|
||||
imm.showSoftInput(
|
||||
mSearchEditText, InputMethodManager.SHOW_IMPLICIT);
|
||||
}
|
||||
});
|
||||
if (binding.speedDial.isOpen()) {
|
||||
|
@ -161,7 +173,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemActionCollapse(MenuItem item) {
|
||||
public boolean onMenuItemActionCollapse(@NonNull final MenuItem item) {
|
||||
SoftKeyboardUtils.hideSoftKeyboard(StartConversationActivity.this);
|
||||
mSearchEditText.setText("");
|
||||
filter(null);
|
||||
|
@ -169,7 +181,8 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
|||
return true;
|
||||
}
|
||||
};
|
||||
private final TextWatcher mSearchTextWatcher = new TextWatcher() {
|
||||
private final TextWatcher mSearchTextWatcher =
|
||||
new TextWatcher() {
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable editable) {
|
||||
|
@ -177,15 +190,14 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
|||
}
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
||||
}
|
||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
||||
}
|
||||
public void onTextChanged(CharSequence s, int start, int before, int count) {}
|
||||
};
|
||||
private MenuItem mMenuSearchView;
|
||||
private final ListItemAdapter.OnTagClickedListener mOnTagClickedListener = new ListItemAdapter.OnTagClickedListener() {
|
||||
private final ListItemAdapter.OnTagClickedListener mOnTagClickedListener =
|
||||
new ListItemAdapter.OnTagClickedListener() {
|
||||
@Override
|
||||
public void onTagClicked(String tag) {
|
||||
if (mMenuSearchView != null) {
|
||||
|
@ -198,10 +210,12 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
|||
};
|
||||
private Pair<Integer, Intent> mPostponedActivityResult;
|
||||
private Toast mToast;
|
||||
private final UiCallback<Conversation> mAdhocConferenceCallback = new UiCallback<Conversation>() {
|
||||
private final UiCallback<Conversation> mAdhocConferenceCallback =
|
||||
new UiCallback<>() {
|
||||
@Override
|
||||
public void success(final Conversation conversation) {
|
||||
runOnUiThread(() -> {
|
||||
runOnUiThread(
|
||||
() -> {
|
||||
hideToast();
|
||||
switchToConversation(conversation);
|
||||
});
|
||||
|
@ -213,12 +227,11 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
|||
}
|
||||
|
||||
@Override
|
||||
public void userInputRequired(PendingIntent pi, Conversation object) {
|
||||
|
||||
}
|
||||
public void userInputRequired(PendingIntent pi, Conversation object) {}
|
||||
};
|
||||
private ActivityStartConversationBinding binding;
|
||||
private final TextView.OnEditorActionListener mSearchDone = new TextView.OnEditorActionListener() {
|
||||
private final TextView.OnEditorActionListener mSearchDone =
|
||||
new TextView.OnEditorActionListener() {
|
||||
@Override
|
||||
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
|
||||
int pos = binding.startConversationViewPager.getCurrentItem();
|
||||
|
@ -226,7 +239,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
|||
if (contacts.size() == 1) {
|
||||
openConversation(contacts.get(0));
|
||||
return true;
|
||||
} else if (contacts.size() == 0 && conferences.size() == 1) {
|
||||
} else if (contacts.isEmpty() && conferences.size() == 1) {
|
||||
openConversationsForBookmark((Bookmark) conferences.get(0));
|
||||
return true;
|
||||
}
|
||||
|
@ -234,7 +247,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
|||
if (conferences.size() == 1) {
|
||||
openConversationsForBookmark((Bookmark) conferences.get(0));
|
||||
return true;
|
||||
} else if (conferences.size() == 0 && contacts.size() == 1) {
|
||||
} else if (conferences.isEmpty() && contacts.size() == 1) {
|
||||
openConversation(contacts.get(0));
|
||||
return true;
|
||||
}
|
||||
|
@ -245,16 +258,22 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
|||
}
|
||||
};
|
||||
|
||||
public static void populateAccountSpinner(final Context context, final List<String> accounts, final AutoCompleteTextView spinner) {
|
||||
public static void populateAccountSpinner(
|
||||
final Context context,
|
||||
final List<String> accounts,
|
||||
final AutoCompleteTextView spinner) {
|
||||
if (accounts.isEmpty()) {
|
||||
ArrayAdapter<String> adapter = new ArrayAdapter<>(context,
|
||||
ArrayAdapter<String> adapter =
|
||||
new ArrayAdapter<>(
|
||||
context,
|
||||
R.layout.item_autocomplete,
|
||||
Collections.singletonList(context.getString(R.string.no_accounts)));
|
||||
adapter.setDropDownViewResource(R.layout.item_autocomplete);
|
||||
spinner.setAdapter(adapter);
|
||||
spinner.setEnabled(false);
|
||||
} else {
|
||||
final ArrayAdapter<String> adapter = new ArrayAdapter<>(context, R.layout.item_autocomplete, accounts);
|
||||
final ArrayAdapter<String> adapter =
|
||||
new ArrayAdapter<>(context, R.layout.item_autocomplete, accounts);
|
||||
adapter.setDropDownViewResource(R.layout.item_autocomplete);
|
||||
spinner.setAdapter(adapter);
|
||||
spinner.setEnabled(true);
|
||||
|
@ -275,7 +294,10 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
|||
}
|
||||
|
||||
private static boolean isViewIntent(final Intent i) {
|
||||
return i != null && (Intent.ACTION_VIEW.equals(i.getAction()) || Intent.ACTION_SENDTO.equals(i.getAction()) || i.hasExtra(EXTRA_INVITE_URI));
|
||||
return i != null
|
||||
&& (Intent.ACTION_VIEW.equals(i.getAction())
|
||||
|| Intent.ACTION_SENDTO.equals(i.getAction())
|
||||
|| i.hasExtra(EXTRA_INVITE_URI));
|
||||
}
|
||||
|
||||
protected void hideToast() {
|
||||
|
@ -305,7 +327,8 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
|||
|
||||
inflateFab(binding.speedDial, R.menu.start_conversation_fab_submenu);
|
||||
binding.tabLayout.setupWithViewPager(binding.startConversationViewPager);
|
||||
binding.startConversationViewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
|
||||
binding.startConversationViewPager.addOnPageChangeListener(
|
||||
new ViewPager.SimpleOnPageChangeListener() {
|
||||
@Override
|
||||
public void onPageSelected(int position) {
|
||||
updateSearchViewHint();
|
||||
|
@ -320,9 +343,13 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
|||
|
||||
final SharedPreferences preferences = getPreferences();
|
||||
|
||||
this.mHideOfflineContacts = QuickConversationsService.isConversations() && preferences.getBoolean("hide_offline", false);
|
||||
this.mHideOfflineContacts =
|
||||
QuickConversationsService.isConversations()
|
||||
&& preferences.getBoolean("hide_offline", false);
|
||||
|
||||
final boolean startSearching = preferences.getBoolean("start_searching", getResources().getBoolean(R.bool.start_searching));
|
||||
final boolean startSearching =
|
||||
preferences.getBoolean(
|
||||
"start_searching", getResources().getBoolean(R.bool.start_searching));
|
||||
|
||||
final Intent intent;
|
||||
if (savedInstanceState == null) {
|
||||
|
@ -347,6 +374,66 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
|||
} else if (startSearching && mInitialSearchValue.peek() == null) {
|
||||
mInitialSearchValue.push("");
|
||||
}
|
||||
mRequestedContactsPermission.set(
|
||||
savedInstanceState != null
|
||||
&& savedInstanceState.getBoolean("requested_contacts_permission", false));
|
||||
mOpenedFab.set(
|
||||
savedInstanceState != null && savedInstanceState.getBoolean("opened_fab", false));
|
||||
binding.speedDial.setOnActionSelectedListener(
|
||||
actionItem -> {
|
||||
final String searchString =
|
||||
mSearchEditText != null ? mSearchEditText.getText().toString() : null;
|
||||
final String prefilled;
|
||||
if (isValidJid(searchString)) {
|
||||
prefilled = Jid.ofEscaped(searchString).toEscapedString();
|
||||
} else {
|
||||
prefilled = null;
|
||||
}
|
||||
switch (actionItem.getId()) {
|
||||
case R.id.discover_public_channels:
|
||||
if (QuickConversationsService.isPlayStoreFlavor()) {
|
||||
throw new IllegalStateException(
|
||||
"Channel discovery is not available on Google Play flavor");
|
||||
} else {
|
||||
startActivity(new Intent(this, ChannelDiscoveryActivity.class));
|
||||
}
|
||||
break;
|
||||
case R.id.create_private_group_chat:
|
||||
showCreatePrivateGroupChatDialog();
|
||||
break;
|
||||
case R.id.create_public_channel:
|
||||
showPublicChannelDialog();
|
||||
break;
|
||||
case R.id.create_contact:
|
||||
showCreateContactDialog(prefilled, null);
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
BottomNavigationView bottomNavigationView=findViewById(R.id.bottom_navigation);
|
||||
bottomNavigationView.setOnItemSelectedListener(item -> {
|
||||
|
||||
switch (item.getItemId()) {
|
||||
case R.id.chats -> {
|
||||
startActivity(new Intent(getApplicationContext(), ConversationsActivity.class));
|
||||
overridePendingTransition(R.animator.fade_in, R.animator.fade_out);
|
||||
return true;
|
||||
}
|
||||
case R.id.contactslist -> {
|
||||
return true;
|
||||
}
|
||||
case R.id.manageaccounts -> {
|
||||
Intent i = new Intent(getApplicationContext(), MANAGE_ACCOUNT_ACTIVITY);
|
||||
i.putExtra("show_nav_bar", true);
|
||||
startActivity(i);
|
||||
overridePendingTransition(R.animator.fade_in, R.animator.fade_out);
|
||||
return true;
|
||||
}
|
||||
default ->
|
||||
throw new IllegalStateException("Unexpected value: " + item.getItemId());
|
||||
}
|
||||
});
|
||||
mRequestedContactsPermission.set(savedInstanceState != null && savedInstanceState.getBoolean("requested_contacts_permission", false));
|
||||
mOpenedFab.set(savedInstanceState != null && savedInstanceState.getBoolean("opened_fab", false));
|
||||
binding.speedDial.setOnActionSelectedListener(actionItem -> {
|
||||
|
@ -410,16 +497,29 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
|||
final Menu menu = popupMenu.getMenu();
|
||||
for (int i = 0; i < menu.size(); i++) {
|
||||
final MenuItem menuItem = menu.getItem(i);
|
||||
if (QuickConversationsService.isPlayStoreFlavor() && menuItem.getItemId() == R.id.discover_public_channels) {
|
||||
if (QuickConversationsService.isPlayStoreFlavor()
|
||||
&& menuItem.getItemId() == R.id.discover_public_channels) {
|
||||
continue;
|
||||
}
|
||||
final SpeedDialActionItem actionItem = new SpeedDialActionItem.Builder(menuItem.getItemId(), menuItem.getIcon())
|
||||
.setLabel(menuItem.getTitle() != null ? menuItem.getTitle().toString() : null)
|
||||
.setFabImageTintColor(MaterialColors.getColor(speedDialView, com.google.android.material.R.attr.colorOnSurface))
|
||||
.setFabBackgroundColor(MaterialColors.getColor(speedDialView, com.google.android.material.R.attr.colorSurfaceContainerHighest))
|
||||
final SpeedDialActionItem actionItem =
|
||||
new SpeedDialActionItem.Builder(menuItem.getItemId(), menuItem.getIcon())
|
||||
.setLabel(
|
||||
menuItem.getTitle() != null
|
||||
? menuItem.getTitle().toString()
|
||||
: null)
|
||||
.setFabImageTintColor(
|
||||
MaterialColors.getColor(
|
||||
speedDialView,
|
||||
com.google.android.material.R.attr.colorOnSurface))
|
||||
.setFabBackgroundColor(
|
||||
MaterialColors.getColor(
|
||||
speedDialView,
|
||||
com.google.android.material.R.attr
|
||||
.colorSurfaceContainerHighest))
|
||||
.create();
|
||||
speedDialView.addActionItem(actionItem);
|
||||
}
|
||||
speedDialView.setContentDescription(getString(R.string.add_contact_or_create_or_join_group_chat));
|
||||
}
|
||||
|
||||
public static boolean isValidJid(String input) {
|
||||
|
@ -434,12 +534,16 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
|||
@Override
|
||||
public void onSaveInstanceState(Bundle savedInstanceState) {
|
||||
Intent pendingIntent = pendingViewIntent.peek();
|
||||
savedInstanceState.putParcelable("intent", pendingIntent != null ? pendingIntent : getIntent());
|
||||
savedInstanceState.putBoolean("requested_contacts_permission", mRequestedContactsPermission.get());
|
||||
savedInstanceState.putParcelable(
|
||||
"intent", pendingIntent != null ? pendingIntent : getIntent());
|
||||
savedInstanceState.putBoolean(
|
||||
"requested_contacts_permission", mRequestedContactsPermission.get());
|
||||
savedInstanceState.putBoolean("opened_fab", mOpenedFab.get());
|
||||
savedInstanceState.putBoolean("created_by_view_intent", createdByViewIntent);
|
||||
if (mMenuSearchView != null && mMenuSearchView.isActionViewExpanded()) {
|
||||
savedInstanceState.putString("search", mSearchEditText != null ? mSearchEditText.getText().toString() : null);
|
||||
savedInstanceState.putString(
|
||||
"search",
|
||||
mSearchEditText != null ? mSearchEditText.getText().toString() : null);
|
||||
}
|
||||
super.onSaveInstanceState(savedInstanceState);
|
||||
}
|
||||
|
@ -447,11 +551,24 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
|||
@Override
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
if (pendingViewIntent.peek() == null) {
|
||||
askForContactsPermissions();
|
||||
}
|
||||
mConferenceAdapter.refreshSettings();
|
||||
mContactsAdapter.refreshSettings();
|
||||
if (pendingViewIntent.peek() == null) {
|
||||
if (askForContactsPermissions()) {
|
||||
return;
|
||||
}
|
||||
requestNotificationPermissionIfNeeded();
|
||||
}
|
||||
}
|
||||
|
||||
private void requestNotificationPermissionIfNeeded() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
|
||||
&& ActivityCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS)
|
||||
!= PackageManager.PERMISSION_GRANTED) {
|
||||
requestPermissions(
|
||||
new String[] {Manifest.permission.POST_NOTIFICATIONS},
|
||||
REQUEST_POST_NOTIFICATION);
|
||||
}
|
||||
|
||||
BottomNavigationView bottomNavigationView=findViewById(R.id.bottom_navigation);
|
||||
bottomNavigationView.setSelectedItemId(R.id.contactslist);
|
||||
|
@ -487,7 +604,9 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
|||
}
|
||||
|
||||
protected void openConversationForContact(Contact contact) {
|
||||
Conversation conversation = xmppConnectionService.findOrCreateConversation(contact.getAccount(), contact.getJid(), false, true);
|
||||
Conversation conversation =
|
||||
xmppConnectionService.findOrCreateConversation(
|
||||
contact.getAccount(), contact.getJid(), false, true);
|
||||
SoftKeyboardUtils.hideSoftKeyboard(this);
|
||||
switchToConversation(conversation);
|
||||
}
|
||||
|
@ -512,9 +631,11 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
|||
shareIntent.putExtra(Intent.EXTRA_TEXT, "xmpp:" + Uri.encode(address, "@/+") + "?join");
|
||||
shareIntent.setType("text/plain");
|
||||
try {
|
||||
context.startActivity(Intent.createChooser(shareIntent, context.getText(R.string.share_uri_with)));
|
||||
context.startActivity(
|
||||
Intent.createChooser(shareIntent, context.getText(R.string.share_uri_with)));
|
||||
} catch (ActivityNotFoundException e) {
|
||||
Toast.makeText(context, R.string.no_application_to_share_uri, Toast.LENGTH_SHORT).show();
|
||||
Toast.makeText(context, R.string.no_application_to_share_uri, Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -524,7 +645,9 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
|||
Toast.makeText(this, R.string.invalid_jid, Toast.LENGTH_SHORT).show();
|
||||
return;
|
||||
}
|
||||
final Conversation conversation = xmppConnectionService.findOrCreateConversation(bookmark.getAccount(), jid, true, true, true);
|
||||
final Conversation conversation =
|
||||
xmppConnectionService.findOrCreateConversation(
|
||||
bookmark.getAccount(), jid, true, true, true);
|
||||
bookmark.setConversation(conversation);
|
||||
if (!bookmark.autojoin()) {
|
||||
bookmark.setAutojoin(true);
|
||||
|
@ -551,8 +674,12 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
|||
final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
|
||||
builder.setNegativeButton(R.string.cancel, null);
|
||||
builder.setTitle(R.string.action_delete_contact);
|
||||
builder.setMessage(JidDialog.style(this, R.string.remove_contact_text, contact.getJid().toEscapedString()));
|
||||
builder.setPositiveButton(R.string.delete, (dialog, which) -> {
|
||||
builder.setMessage(
|
||||
JidDialog.style(
|
||||
this, R.string.remove_contact_text, contact.getJid().toEscapedString()));
|
||||
builder.setPositiveButton(
|
||||
R.string.delete,
|
||||
(dialog, which) -> {
|
||||
xmppConnectionService.deleteContactOnServer(contact);
|
||||
filter(mSearchEditText.getText().toString());
|
||||
});
|
||||
|
@ -567,11 +694,19 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
|||
builder.setNegativeButton(R.string.cancel, null);
|
||||
builder.setTitle(R.string.delete_bookmark);
|
||||
if (hasConversation) {
|
||||
builder.setMessage(JidDialog.style(this, R.string.remove_bookmark_and_close, bookmark.getJid().toEscapedString()));
|
||||
builder.setMessage(
|
||||
JidDialog.style(
|
||||
this,
|
||||
R.string.remove_bookmark_and_close,
|
||||
bookmark.getJid().toEscapedString()));
|
||||
} else {
|
||||
builder.setMessage(JidDialog.style(this, R.string.remove_bookmark, bookmark.getJid().toEscapedString()));
|
||||
builder.setMessage(
|
||||
JidDialog.style(
|
||||
this, R.string.remove_bookmark, bookmark.getJid().toEscapedString()));
|
||||
}
|
||||
builder.setPositiveButton(hasConversation ? R.string.delete_and_close : R.string.delete, (dialog, which) -> {
|
||||
builder.setPositiveButton(
|
||||
hasConversation ? R.string.delete_and_close : R.string.delete,
|
||||
(dialog, which) -> {
|
||||
bookmark.setConversation(null);
|
||||
final Account account = bookmark.getAccount();
|
||||
xmppConnectionService.deleteBookmark(account, bookmark);
|
||||
|
@ -581,7 +716,6 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
|||
filter(mSearchEditText.getText().toString());
|
||||
});
|
||||
builder.create().show();
|
||||
|
||||
}
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
|
@ -678,7 +812,8 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
|||
ft.remove(prev);
|
||||
}
|
||||
ft.addToBackStack(null);
|
||||
JoinConferenceDialog joinConferenceFragment = JoinConferenceDialog.newInstance(prefilledJid, invite.getParameter("password"), mActivatedAccounts);
|
||||
JoinConferenceDialog joinConferenceFragment =
|
||||
JoinConferenceDialog.newInstance(prefilledJid, invite.getParameter("password"), mActivatedAccounts);
|
||||
joinConferenceFragment.show(ft, FRAGMENT_TAG_DIALOG);
|
||||
}
|
||||
|
||||
|
@ -689,7 +824,8 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
|||
ft.remove(prev);
|
||||
}
|
||||
ft.addToBackStack(null);
|
||||
CreatePrivateGroupChatDialog createConferenceFragment = CreatePrivateGroupChatDialog.newInstance(mActivatedAccounts);
|
||||
CreatePrivateGroupChatDialog createConferenceFragment =
|
||||
CreatePrivateGroupChatDialog.newInstance(mActivatedAccounts);
|
||||
createConferenceFragment.show(ft, FRAGMENT_TAG_DIALOG);
|
||||
}
|
||||
|
||||
|
@ -700,11 +836,13 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
|||
ft.remove(prev);
|
||||
}
|
||||
ft.addToBackStack(null);
|
||||
CreatePublicChannelDialog dialog = CreatePublicChannelDialog.newInstance(mActivatedAccounts);
|
||||
CreatePublicChannelDialog dialog =
|
||||
CreatePublicChannelDialog.newInstance(mActivatedAccounts);
|
||||
dialog.show(ft, FRAGMENT_TAG_DIALOG);
|
||||
}
|
||||
|
||||
public static Account getSelectedAccount(final Context context, final AutoCompleteTextView spinner) {
|
||||
public static Account getSelectedAccount(
|
||||
final Context context, final AutoCompleteTextView spinner) {
|
||||
if (spinner == null || !spinner.isEnabled()) {
|
||||
return null;
|
||||
}
|
||||
|
@ -726,7 +864,9 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
|||
}
|
||||
|
||||
protected void switchToConversation(Contact contact) {
|
||||
Conversation conversation = xmppConnectionService.findOrCreateConversation(contact.getAccount(), contact.getJid(), false, true);
|
||||
Conversation conversation =
|
||||
xmppConnectionService.findOrCreateConversation(
|
||||
contact.getAccount(), contact.getJid(), false, true);
|
||||
switchToConversation(conversation);
|
||||
}
|
||||
|
||||
|
@ -735,7 +875,9 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
|||
}
|
||||
|
||||
protected void switchToConversationDoNotAppend(Contact contact, String body, String postInit) {
|
||||
Conversation conversation = xmppConnectionService.findOrCreateConversation(contact.getAccount(), contact.getJid(), false, true);
|
||||
Conversation conversation =
|
||||
xmppConnectionService.findOrCreateConversation(
|
||||
contact.getAccount(), contact.getJid(), false, true);
|
||||
switchToConversation(conversation, body, false, null, false, true, postInit);
|
||||
}
|
||||
|
||||
|
@ -881,7 +1023,8 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
|||
this.mPostponedActivityResult = null;
|
||||
if (requestCode == REQUEST_CREATE_CONFERENCE) {
|
||||
Account account = extractAccount(intent);
|
||||
final String name = intent.getStringExtra(ChooseContactActivity.EXTRA_GROUP_CHAT_NAME);
|
||||
final String name =
|
||||
intent.getStringExtra(ChooseContactActivity.EXTRA_GROUP_CHAT_NAME);
|
||||
final List<Jid> jids = ChooseContactActivity.extractJabberIds(intent);
|
||||
if (account != null && jids.size() > 0) {
|
||||
// This hardcodes cheogram.com and is in general a terrible hack
|
||||
|
@ -916,11 +1059,21 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
|||
super.onActivityResult(requestCode, requestCode, intent);
|
||||
}
|
||||
|
||||
private void askForContactsPermissions() {
|
||||
if (QuickConversationsService.isContactListIntegration(this)) {
|
||||
private boolean askForContactsPermissions() {
|
||||
if (!QuickConversationsService.isContactListIntegration(this)) {
|
||||
return false;
|
||||
}
|
||||
if (checkSelfPermission(Manifest.permission.READ_CONTACTS)
|
||||
!= PackageManager.PERMISSION_GRANTED) {
|
||||
== PackageManager.PERMISSION_GRANTED) {
|
||||
return false;
|
||||
}
|
||||
if (mRequestedContactsPermission.compareAndSet(false, true)) {
|
||||
final ImmutableList.Builder<String> permissionBuilder = new ImmutableList.Builder<>();
|
||||
permissionBuilder.add(Manifest.permission.READ_CONTACTS);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
||||
permissionBuilder.add(Manifest.permission.POST_NOTIFICATIONS);
|
||||
}
|
||||
final String[] permission = permissionBuilder.build().toArray(new String[0]);
|
||||
final String consent =
|
||||
PreferenceManager.getDefaultSharedPreferences(getApplicationContext())
|
||||
.getString(PREF_KEY_CONTACT_INTEGRATION_CONSENT, null);
|
||||
|
@ -929,19 +1082,19 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
|||
|| QuickConversationsService.isPlayStoreFlavor())
|
||||
&& !"agreed".equals(consent);
|
||||
if (requiresConsent && "declined".equals(consent)) {
|
||||
Log.d(Config.LOGTAG,"not asking for contacts permission because consent has been declined");
|
||||
return;
|
||||
Log.d(
|
||||
Config.LOGTAG,
|
||||
"not asking for contacts permission because consent has been declined");
|
||||
return false;
|
||||
}
|
||||
if (requiresConsent
|
||||
|| shouldShowRequestPermissionRationale(
|
||||
Manifest.permission.READ_CONTACTS)) {
|
||||
|| shouldShowRequestPermissionRationale(Manifest.permission.READ_CONTACTS)) {
|
||||
final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
|
||||
final AtomicBoolean requestPermission = new AtomicBoolean(false);
|
||||
if (QuickConversationsService.isQuicksy()) {
|
||||
builder.setTitle(R.string.quicksy_wants_your_consent);
|
||||
builder.setMessage(
|
||||
Html.fromHtml(
|
||||
getString(R.string.sync_with_contacts_quicksy_static)));
|
||||
Html.fromHtml(getString(R.string.sync_with_contacts_quicksy_static)));
|
||||
} else {
|
||||
builder.setTitle(R.string.sync_with_contacts);
|
||||
builder.setMessage(
|
||||
|
@ -962,32 +1115,29 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
|||
PreferenceManager.getDefaultSharedPreferences(
|
||||
getApplicationContext())
|
||||
.edit()
|
||||
.putString(
|
||||
PREF_KEY_CONTACT_INTEGRATION_CONSENT, "agreed")
|
||||
.putString(PREF_KEY_CONTACT_INTEGRATION_CONSENT, "agreed")
|
||||
.apply();
|
||||
}
|
||||
if (requestPermission.compareAndSet(false, true)) {
|
||||
requestPermissions(
|
||||
new String[] {Manifest.permission.READ_CONTACTS},
|
||||
REQUEST_SYNC_CONTACTS);
|
||||
requestPermissions(permission, REQUEST_SYNC_CONTACTS);
|
||||
}
|
||||
});
|
||||
if (requiresConsent) {
|
||||
builder.setNegativeButton(R.string.decline, (dialog, which) -> PreferenceManager.getDefaultSharedPreferences(
|
||||
builder.setNegativeButton(
|
||||
R.string.decline,
|
||||
(dialog, which) ->
|
||||
PreferenceManager.getDefaultSharedPreferences(
|
||||
getApplicationContext())
|
||||
.edit()
|
||||
.putString(
|
||||
PREF_KEY_CONTACT_INTEGRATION_CONSENT, "declined")
|
||||
PREF_KEY_CONTACT_INTEGRATION_CONSENT,
|
||||
"declined")
|
||||
.apply());
|
||||
} else {
|
||||
builder.setOnDismissListener(
|
||||
dialog -> {
|
||||
if (requestPermission.compareAndSet(false, true)) {
|
||||
requestPermissions(
|
||||
new String[] {
|
||||
Manifest.permission.READ_CONTACTS
|
||||
},
|
||||
REQUEST_SYNC_CONTACTS);
|
||||
requestPermissions(permission, REQUEST_SYNC_CONTACTS);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -1003,17 +1153,15 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
|||
});
|
||||
dialog.show();
|
||||
} else {
|
||||
requestPermissions(
|
||||
new String[] {Manifest.permission.READ_CONTACTS},
|
||||
REQUEST_SYNC_CONTACTS);
|
||||
}
|
||||
}
|
||||
requestPermissions(permission, REQUEST_SYNC_CONTACTS);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
public void onRequestPermissionsResult(
|
||||
int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
if (grantResults.length > 0)
|
||||
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
|
@ -1033,11 +1181,10 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
|||
if (actionBar == null) {
|
||||
return;
|
||||
}
|
||||
boolean openConversations = !createdByViewIntent && !xmppConnectionService.isConversationsListEmpty(null);
|
||||
boolean showNavBar = binding.bottomNavigation.getVisibility() == VISIBLE;
|
||||
actionBar.setDisplayHomeAsUpEnabled(openConversations && !showNavBar);
|
||||
actionBar.setDisplayHomeAsUpEnabled(openConversations && !showNavBar);
|
||||
|
||||
boolean openConversations =
|
||||
!createdByViewIntent && !xmppConnectionService.isConversationsListEmpty(null);
|
||||
actionBar.setDisplayHomeAsUpEnabled(openConversations);
|
||||
actionBar.setDisplayHomeAsUpEnabled(openConversations);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1049,7 +1196,8 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
|||
xmppConnectionService.getQuickConversationsService().considerSyncBackground(false);
|
||||
}
|
||||
if (mPostponedActivityResult != null) {
|
||||
onActivityResult(mPostponedActivityResult.first, RESULT_OK, mPostponedActivityResult.second);
|
||||
onActivityResult(
|
||||
mPostponedActivityResult.first, RESULT_OK, mPostponedActivityResult.second);
|
||||
this.mPostponedActivityResult = null;
|
||||
}
|
||||
this.mActivatedAccounts.clear();
|
||||
|
@ -1118,7 +1266,11 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
|||
if (QuickConversationsService.isQuicksy()) {
|
||||
setRefreshing(xmppConnectionService.getQuickConversationsService().isSynchronizing());
|
||||
}
|
||||
if (QuickConversationsService.isConversations() && AccountUtils.hasEnabledAccounts(xmppConnectionService) && this.contacts.size() == 0 && this.conferences.size() == 0 && mOpenedFab.compareAndSet(false, true)) {
|
||||
if (QuickConversationsService.isConversations()
|
||||
&& AccountUtils.hasEnabledAccounts(xmppConnectionService)
|
||||
&& this.contacts.size() == 0
|
||||
&& this.conferences.size() == 0
|
||||
&& mOpenedFab.compareAndSet(false, true)) {
|
||||
binding.speedDial.open();
|
||||
}
|
||||
}
|
||||
|
@ -1141,7 +1293,8 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
|||
case Intent.ACTION_VIEW:
|
||||
Uri uri = intent.getData();
|
||||
if (uri != null) {
|
||||
Invite invite = new Invite(intent.getData(), intent.getBooleanExtra("scanned", false));
|
||||
Invite invite =
|
||||
new Invite(intent.getData(), intent.getBooleanExtra("scanned", false));
|
||||
invite.account = intent.getStringExtra(EXTRA_ACCOUNT);
|
||||
invite.forceDialog = intent.getBooleanExtra("force_dialog", false);
|
||||
return invite.invite();
|
||||
|
@ -1153,7 +1306,8 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
|||
}
|
||||
|
||||
private boolean handleJid(Invite invite) {
|
||||
final List<Contact> contacts = xmppConnectionService.findContacts(invite.getJid(), invite.account);
|
||||
List<Contact> contacts =
|
||||
xmppConnectionService.findContacts(invite.getJid(), invite.account);
|
||||
final Conversation muc = xmppConnectionService.findFirstMuc(invite.getJid(), invite.account);
|
||||
if (invite.isAction(XmppUri.ACTION_JOIN) || (contacts.isEmpty() && muc != null)) {
|
||||
if (muc != null && !invite.forceDialog) {
|
||||
|
@ -1175,8 +1329,10 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
|||
displayVerificationWarningDialog(contact, invite);
|
||||
} else {
|
||||
if (invite.hasFingerprints()) {
|
||||
if (xmppConnectionService.verifyFingerprints(contact, invite.getFingerprints())) {
|
||||
Toast.makeText(this, R.string.verified_fingerprints, Toast.LENGTH_SHORT).show();
|
||||
if (xmppConnectionService.verifyFingerprints(
|
||||
contact, invite.getFingerprints())) {
|
||||
Toast.makeText(this, R.string.verified_fingerprints, Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
}
|
||||
}
|
||||
if (invite.account != null) {
|
||||
|
@ -1204,15 +1360,23 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
|||
View view = getLayoutInflater().inflate(R.layout.dialog_verify_fingerprints, null);
|
||||
final CheckBox isTrustedSource = view.findViewById(R.id.trusted_source);
|
||||
TextView warning = view.findViewById(R.id.warning);
|
||||
warning.setText(JidDialog.style(this, R.string.verifying_omemo_keys_trusted_source, contact.getJid().asBareJid().toEscapedString(), contact.getDisplayName()));
|
||||
warning.setText(
|
||||
JidDialog.style(
|
||||
this,
|
||||
R.string.verifying_omemo_keys_trusted_source,
|
||||
contact.getJid().asBareJid().toEscapedString(),
|
||||
contact.getDisplayName()));
|
||||
builder.setView(view);
|
||||
builder.setPositiveButton(R.string.confirm, (dialog, which) -> {
|
||||
builder.setPositiveButton(
|
||||
R.string.confirm,
|
||||
(dialog, which) -> {
|
||||
if (isTrustedSource.isChecked() && invite.hasFingerprints()) {
|
||||
xmppConnectionService.verifyFingerprints(contact, invite.getFingerprints());
|
||||
}
|
||||
switchToConversationDoNotAppend(contact, invite.getBody());
|
||||
});
|
||||
builder.setNegativeButton(R.string.cancel, (dialog, which) -> StartConversationActivity.this.finish());
|
||||
builder.setNegativeButton(
|
||||
R.string.cancel, (dialog, which) -> StartConversationActivity.this.finish());
|
||||
AlertDialog dialog = builder.create();
|
||||
dialog.setCanceledOnTouchOutside(false);
|
||||
dialog.setOnCancelListener(dialog1 -> StartConversationActivity.this.finish());
|
||||
|
@ -1235,7 +1399,8 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
|||
if (account.isEnabled()) {
|
||||
for (Contact contact : account.getRoster().getContacts()) {
|
||||
Presence.Status s = contact.getShownStatus();
|
||||
if (contact.showInContactList() && contact.match(this, needle)
|
||||
if (contact.showInContactList()
|
||||
&& contact.match(this, needle)
|
||||
&& (!this.mHideOfflineContacts
|
||||
|| (needle != null && !needle.trim().isEmpty())
|
||||
|| s.compareTo(Presence.Status.OFFLINE) < 0)) {
|
||||
|
@ -1330,7 +1495,9 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
|||
}
|
||||
|
||||
private void navigateBack() {
|
||||
if (!createdByViewIntent && xmppConnectionService != null && !xmppConnectionService.isConversationsListEmpty(null)) {
|
||||
if (!createdByViewIntent
|
||||
&& xmppConnectionService != null
|
||||
&& !xmppConnectionService.isConversationsListEmpty(null)) {
|
||||
Intent intent = new Intent(this, ConversationsActivity.class);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
|
||||
startActivity(intent);
|
||||
|
@ -1354,13 +1521,20 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
|||
intent.putExtra(ChooseContactActivity.EXTRA_SHOW_ENTER_JID, false);
|
||||
intent.putExtra(ChooseContactActivity.EXTRA_SELECT_MULTIPLE, true);
|
||||
intent.putExtra(ChooseContactActivity.EXTRA_GROUP_CHAT_NAME, name.trim());
|
||||
intent.putExtra(ChooseContactActivity.EXTRA_ACCOUNT, account.getJid().asBareJid().toEscapedString());
|
||||
intent.putExtra(
|
||||
ChooseContactActivity.EXTRA_ACCOUNT,
|
||||
account.getJid().asBareJid().toEscapedString());
|
||||
intent.putExtra(ChooseContactActivity.EXTRA_TITLE_RES_ID, R.string.choose_participants);
|
||||
startActivityForResult(intent, REQUEST_CREATE_CONFERENCE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onJoinDialogPositiveClick(Dialog dialog, AutoCompleteTextView spinner, TextInputLayout layout, AutoCompleteTextView jid, String password) {
|
||||
public void onJoinDialogPositiveClick(
|
||||
final Dialog dialog,
|
||||
final AutoCompleteTextView spinner,
|
||||
final TextInputLayout layout,
|
||||
final AutoCompleteTextView jid,
|
||||
final String password) {
|
||||
if (!xmppConnectionServiceBound) {
|
||||
return;
|
||||
}
|
||||
|
@ -1395,9 +1569,9 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
|||
bookmark.setNick(nick);
|
||||
}
|
||||
xmppConnectionService.createBookmark(account, bookmark);
|
||||
final Conversation conversation = xmppConnectionService
|
||||
.findOrCreateConversation(account, conferenceJid, true, true, null, true, password);
|
||||
|
||||
final Conversation conversation =
|
||||
xmppConnectionService.findOrCreateConversation(
|
||||
account, conferenceJid, true, true, null, true, password);
|
||||
bookmark.setConversation(conversation);
|
||||
switchToConversation(conversation);
|
||||
}
|
||||
|
@ -1417,7 +1591,6 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
private void setRefreshing(boolean refreshing) {
|
||||
MyListFragment fragment = (MyListFragment) mListPagerAdapter.getItem(0);
|
||||
if (fragment != null) {
|
||||
|
@ -1429,28 +1602,31 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
|||
public void onCreatePublicChannel(Account account, String name, Jid address) {
|
||||
mToast = Toast.makeText(this, R.string.creating_channel, Toast.LENGTH_LONG);
|
||||
mToast.show();
|
||||
xmppConnectionService.createPublicChannel(account, name, address, new UiCallback<Conversation>() {
|
||||
xmppConnectionService.createPublicChannel(
|
||||
account,
|
||||
name,
|
||||
address,
|
||||
new UiCallback<Conversation>() {
|
||||
@Override
|
||||
public void success(Conversation conversation) {
|
||||
runOnUiThread(() -> {
|
||||
runOnUiThread(
|
||||
() -> {
|
||||
hideToast();
|
||||
switchToConversation(conversation);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void error(int errorCode, Conversation conversation) {
|
||||
runOnUiThread(() -> {
|
||||
runOnUiThread(
|
||||
() -> {
|
||||
replaceToast(getString(errorCode));
|
||||
switchToConversation(conversation);
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void userInputRequired(PendingIntent pi, Conversation object) {
|
||||
|
||||
}
|
||||
public void userInputRequired(PendingIntent pi, Conversation object) {}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1463,7 +1639,8 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onListItemClick(final ListView l, final View v, final int position, final long id) {
|
||||
public void onListItemClick(
|
||||
final ListView l, final View v, final int position, final long id) {
|
||||
if (mOnItemClickListener != null) {
|
||||
mOnItemClickListener.onItemClick(l, v, position, id);
|
||||
}
|
||||
|
@ -1483,7 +1660,10 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(@NonNull final ContextMenu menu, @NonNull final View v, final ContextMenuInfo menuInfo) {
|
||||
public void onCreateContextMenu(
|
||||
@NonNull final ContextMenu menu,
|
||||
@NonNull final View v,
|
||||
final ContextMenuInfo menuInfo) {
|
||||
super.onCreateContextMenu(menu, v, menuInfo);
|
||||
final StartConversationActivity activity = (StartConversationActivity) getActivity();
|
||||
if (activity == null) {
|
||||
|
@ -1517,7 +1697,9 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
|||
if (contact.isSelf()) {
|
||||
showContactDetailsItem.setVisible(false);
|
||||
}
|
||||
deleteContactMenuItem.setVisible(contact.showInRoster() && !contact.getOption(Contact.Options.SYNCED_VIA_OTHER));
|
||||
deleteContactMenuItem.setVisible(
|
||||
contact.showInRoster()
|
||||
&& !contact.getOption(Contact.Options.SYNCED_VIA_OTHER));
|
||||
final XmppConnection xmpp = contact.getAccount().getXmppConnection();
|
||||
if (xmpp != null && xmpp.getFeatures().blocking() && !contact.isSelf()) {
|
||||
if (contact.isBlocked()) {
|
||||
|
@ -1576,7 +1758,8 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
|||
}
|
||||
|
||||
@Override
|
||||
public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) {
|
||||
public void destroyItem(
|
||||
@NonNull ViewGroup container, int position, @NonNull Object object) {
|
||||
FragmentTransaction trans = fragmentManager.beginTransaction();
|
||||
trans.remove(fragments[position]);
|
||||
trans.commit();
|
||||
|
@ -1626,11 +1809,13 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
|||
if (position == 1) {
|
||||
listFragment.setListAdapter(mConferenceAdapter);
|
||||
listFragment.setContextMenu(R.menu.conference_context);
|
||||
listFragment.setOnListItemClickListener((arg0, arg1, p, arg3) -> openConversationForBookmark(p));
|
||||
listFragment.setOnListItemClickListener(
|
||||
(arg0, arg1, p, arg3) -> openConversationForBookmark(p));
|
||||
} else {
|
||||
listFragment.setListAdapter(mContactsAdapter);
|
||||
listFragment.setContextMenu(R.menu.contact_context);
|
||||
listFragment.setOnListItemClickListener((arg0, arg1, p, arg3) -> openConversationForContact(p));
|
||||
listFragment.setOnListItemClickListener(
|
||||
(arg0, arg1, p, arg3) -> openConversationForContact(p));
|
||||
if (QuickConversationsService.isQuicksy()) {
|
||||
listFragment.setOnRefreshListener(StartConversationActivity.this);
|
||||
}
|
||||
|
@ -1654,7 +1839,6 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
|||
|
||||
boolean forceDialog = false;
|
||||
|
||||
|
||||
Invite(final String uri) {
|
||||
super(uri);
|
||||
}
|
||||
|
@ -1665,7 +1849,11 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
|||
|
||||
boolean invite() {
|
||||
if (!isValidJid()) {
|
||||
Toast.makeText(StartConversationActivity.this, R.string.invalid_jid, Toast.LENGTH_SHORT).show();
|
||||
Toast.makeText(
|
||||
StartConversationActivity.this,
|
||||
R.string.invalid_jid,
|
||||
Toast.LENGTH_SHORT)
|
||||
.show();
|
||||
return false;
|
||||
}
|
||||
if (getJid() != null) {
|
||||
|
|
|
@ -68,9 +68,7 @@ public class UriHandlerActivity extends BaseActivity {
|
|||
}
|
||||
|
||||
public static void scan(final Activity activity, final boolean provisioning) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M
|
||||
|| ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA)
|
||||
== PackageManager.PERMISSION_GRANTED) {
|
||||
if (ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
|
||||
final Intent intent = new Intent(activity, UriHandlerActivity.class);
|
||||
intent.setAction(UriHandlerActivity.ACTION_SCAN_QR_CODE);
|
||||
if (provisioning) {
|
||||
|
@ -114,6 +112,7 @@ public class UriHandlerActivity extends BaseActivity {
|
|||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
this.binding = DataBindingUtil.setContentView(this, R.layout.activity_uri_handler);
|
||||
Activities.setStatusAndNavigationBarColors(this, binding.getRoot());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -187,7 +186,7 @@ public class UriHandlerActivity extends BaseActivity {
|
|||
startActivity(intent);
|
||||
return true;
|
||||
}
|
||||
if (accounts.size() == 0
|
||||
if (accounts.isEmpty()
|
||||
&& xmppUri.isAction(XmppUri.ACTION_ROSTER)
|
||||
&& "y"
|
||||
.equalsIgnoreCase(
|
||||
|
@ -203,7 +202,7 @@ public class UriHandlerActivity extends BaseActivity {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (accounts.size() == 0) {
|
||||
if (accounts.isEmpty()) {
|
||||
if (xmppUri.isValidJid()) {
|
||||
intent = SignupUtils.getSignUpIntent(this);
|
||||
intent.putExtra(StartConversationActivity.EXTRA_INVITE_URI, xmppUri.toString());
|
||||
|
@ -259,14 +258,14 @@ public class UriHandlerActivity extends BaseActivity {
|
|||
private void checkForLinkHeader(final HttpUrl url) {
|
||||
Log.d(Config.LOGTAG, "checking for link header on " + url);
|
||||
this.call =
|
||||
HttpConnectionManager.OK_HTTP_CLIENT.newCall(
|
||||
HttpConnectionManager.okHttpClient(this).newCall(
|
||||
new Request.Builder().url(url).head().build());
|
||||
this.call.enqueue(
|
||||
new Callback() {
|
||||
@Override
|
||||
public void onFailure(@NonNull Call call, @NonNull IOException 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
|
||||
|
@ -277,7 +276,7 @@ public class UriHandlerActivity extends BaseActivity {
|
|||
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);
|
||||
}
|
||||
|
||||
private void showErrorOnUiThread(@StringRes int error) {
|
||||
runOnUiThread(()-> showError(error));
|
||||
}
|
||||
|
||||
private static Class<?> findShareViaAccountClass() {
|
||||
try {
|
||||
return Class.forName("eu.siacs.conversations.ui.ShareViaAccountActivity");
|
||||
|
|
|
@ -523,13 +523,8 @@ public abstract class XmppActivity extends ActionBarActivity {
|
|||
}
|
||||
|
||||
protected boolean isOptimizingBattery() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
final PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE);
|
||||
return pm != null
|
||||
&& !pm.isIgnoringBatteryOptimizations(getPackageName());
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
final PowerManager pm = getSystemService(PowerManager.class);
|
||||
return !pm.isIgnoringBatteryOptimizations(getPackageName());
|
||||
}
|
||||
|
||||
protected boolean isAffectedByDataSaver() {
|
||||
|
|
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);
|
||||
}
|
||||
|
||||
static class ConversationViewHolder extends RecyclerView.ViewHolder {
|
||||
private final ItemConversationBinding binding;
|
||||
public static class ConversationViewHolder extends RecyclerView.ViewHolder {
|
||||
public final ItemConversationBinding binding;
|
||||
|
||||
private ConversationViewHolder(final ItemConversationBinding binding) {
|
||||
super(binding.getRoot());
|
||||
|
|
|
@ -45,6 +45,14 @@ public class MediaAdapter extends RecyclerView.Adapter<MediaAdapter.MediaViewHol
|
|||
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
||||
"text/x-tex",
|
||||
"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");
|
||||
|
||||
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;
|
||||
} else if (mime.equals("application/vnd.android.package-archive")) {
|
||||
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;
|
||||
} else if (mime.equals("application/epub+zip")
|
||||
|| mime.equals("application/vnd.amazon.mobi8-ebook")) {
|
||||
|
|
|
@ -14,10 +14,13 @@ import androidx.activity.result.ActivityResultLauncher;
|
|||
import androidx.activity.result.contract.ActivityResultContracts;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.core.content.ContextCompat;
|
||||
import androidx.preference.ListPreference;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
|
||||
import de.monocles.chat.DownloadDefaultStickers;
|
||||
|
||||
import eu.siacs.conversations.R;
|
||||
import eu.siacs.conversations.utils.UIHelper;
|
||||
|
||||
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());
|
||||
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() {
|
||||
|
|
|
@ -196,7 +196,12 @@ public class NotificationsSettingsFragment extends XmppPreferenceFragment {
|
|||
uri = appSettings().getRingtone();
|
||||
}
|
||||
Log.i(Config.LOGTAG, "current ringtone: " + 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() {
|
||||
|
|
|
@ -4,6 +4,7 @@ import android.content.Context;
|
|||
import android.content.SharedPreferences;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.ArrayRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.preference.ListPreference;
|
||||
|
@ -13,6 +14,7 @@ import androidx.preference.Preference;
|
|||
|
||||
import com.rarepebble.colorpicker.ColorPreference;
|
||||
|
||||
import com.google.common.base.Function;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.primitives.Ints;
|
||||
|
||||
|
@ -111,14 +113,29 @@ public abstract class XmppPreferenceFragment extends PreferenceFragmentCompat {
|
|||
}
|
||||
}
|
||||
|
||||
protected static class TimeframeSummaryProvider
|
||||
implements Preference.SummaryProvider<ListPreference> {
|
||||
|
||||
protected void setValues(
|
||||
final ListPreference listPreference,
|
||||
@ArrayRes int resId,
|
||||
final Function<Integer, String> valueToName) {
|
||||
final int[] choices = getResources().getIntArray(resId);
|
||||
final CharSequence[] entries = new CharSequence[choices.length];
|
||||
final CharSequence[] entryValues = new CharSequence[choices.length];
|
||||
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 timeframeValueToName(preference.getContext(), value == null ? 0 : value);
|
||||
}
|
||||
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;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.Signature;
|
||||
import android.util.Log;
|
||||
|
||||
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.Config;
|
||||
|
@ -17,20 +17,16 @@ import eu.siacs.conversations.entities.Message;
|
|||
import eu.siacs.conversations.services.XmppConnectionService;
|
||||
import eu.siacs.conversations.ui.XmppActivity;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.lang.ClassNotFoundException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
||||
public class ExceptionHelper {
|
||||
|
||||
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) {
|
||||
if (Thread.getDefaultUncaughtExceptionHandler() instanceof ExceptionHandler) {
|
||||
|
@ -45,8 +41,8 @@ public class ExceptionHelper {
|
|||
return false;
|
||||
} catch (final ClassNotFoundException e) { }
|
||||
|
||||
try {
|
||||
final XmppConnectionService service = activity == null ? null : activity.xmppConnectionService;
|
||||
final XmppConnectionService service =
|
||||
activity == null ? null : activity.xmppConnectionService;
|
||||
if (service == null) {
|
||||
return false;
|
||||
}
|
||||
|
@ -58,59 +54,52 @@ public class ExceptionHelper {
|
|||
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) {
|
||||
final var file = new File(activity.getCacheDir(), FILENAME);
|
||||
if (!file.exists()) {
|
||||
return false;
|
||||
}
|
||||
String line;
|
||||
while ((line = stacktrace.readLine()) != null) {
|
||||
report.append(line);
|
||||
report.append('\n');
|
||||
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");
|
||||
}
|
||||
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);
|
||||
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.setNegativeButton(
|
||||
activity.getText(R.string.send_never),
|
||||
(dialog, which) -> appSettings.setSendCrashReports(false));
|
||||
builder.create().show();
|
||||
return true;
|
||||
} catch (final IOException ignored) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static void writeToStacktraceFile(Context context, String msg) {
|
||||
static void writeToStacktraceFile(final Context context, final String msg) {
|
||||
try {
|
||||
OutputStream os = context.openFileOutput(FILENAME, Context.MODE_PRIVATE);
|
||||
os.write(msg.getBytes());
|
||||
os.flush();
|
||||
os.close();
|
||||
} catch (IOException ignored) {
|
||||
Files.asCharSink(new File(context.getCacheDir(), FILENAME), Charsets.UTF_8).write(msg);
|
||||
} catch (IOException e) {
|
||||
Log.w(Config.LOGTAG, "could not write stack trace to file", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -141,6 +141,7 @@ public final class MimeUtils {
|
|||
add("application/vnd.sun.xml.writer.global", "sxg");
|
||||
add("application/vnd.sun.xml.writer.template", "stw");
|
||||
add("application/vnd.visio", "vsd");
|
||||
add("application/x-7z-compressed","7z");
|
||||
add("application/x-abiword", "abw");
|
||||
add("application/x-apple-diskimage", "dmg");
|
||||
add("application/x-bcpio", "bcpio");
|
||||
|
|
|
@ -6,24 +6,56 @@ import android.util.Log;
|
|||
|
||||
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.Throwables;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
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.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.lang.reflect.Field;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.Arrays;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
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.DnsCache;
|
||||
import org.minidns.DnsClient;
|
||||
|
@ -44,13 +76,35 @@ import org.minidns.record.Data;
|
|||
import org.minidns.record.InternetAddressRR;
|
||||
import org.minidns.record.Record;
|
||||
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 {
|
||||
|
||||
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;
|
||||
|
||||
private static final String DIRECT_TLS_SERVICE = "_xmpps-client";
|
||||
|
@ -203,7 +257,7 @@ public class Resolver {
|
|||
try {
|
||||
DnsName.from(hostname);
|
||||
return false;
|
||||
} catch (IllegalArgumentException e) {
|
||||
} catch (final InvalidDnsNameException | IllegalArgumentException e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -224,186 +278,212 @@ public class Resolver {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
public static boolean useDirectTls(final int port) {
|
||||
return port == 443 || port == 5223;
|
||||
}
|
||||
|
||||
public static List<Result> resolve(final String domain) {
|
||||
final List<Result> ipResults = fromIpAddress(domain);
|
||||
if (ipResults.size() > 0) {
|
||||
if (!ipResults.isEmpty()) {
|
||||
return ipResults;
|
||||
}
|
||||
final List<Result> results = new ArrayList<>();
|
||||
final List<Result> fallbackResults = new ArrayList<>();
|
||||
final Thread[] threads = new Thread[3];
|
||||
threads[0] = new Thread(() -> {
|
||||
try {
|
||||
final List<Result> list = resolveSrv(domain, true);
|
||||
synchronized (results) {
|
||||
results.addAll(list);
|
||||
}
|
||||
} catch (final Throwable throwable) {
|
||||
if (!(Throwables.getRootCause(throwable) instanceof InterruptedException)) {
|
||||
Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": error resolving SRV record (direct TLS)", throwable);
|
||||
}
|
||||
}
|
||||
});
|
||||
threads[1] = new Thread(() -> {
|
||||
try {
|
||||
final List<Result> list = resolveSrv(domain, false);
|
||||
synchronized (results) {
|
||||
results.addAll(list);
|
||||
}
|
||||
} catch (final Throwable throwable) {
|
||||
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 {
|
||||
threads[0].join();
|
||||
threads[1].join();
|
||||
if (results.size() > 0) {
|
||||
threads[2].interrupt();
|
||||
synchronized (results) {
|
||||
Collections.sort(results);
|
||||
Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": " + results);
|
||||
return results;
|
||||
}
|
||||
|
||||
final var startTls = resolveSrvAsFuture(domain, false);
|
||||
final var directTls = resolveSrvAsFuture(domain, true);
|
||||
|
||||
final var combined = merge(ImmutableList.of(startTls, directTls));
|
||||
|
||||
final var combinedWithFallback =
|
||||
Futures.transformAsync(
|
||||
combined,
|
||||
results -> {
|
||||
if (results.isEmpty()) {
|
||||
return resolveNoSrvAsFuture(DnsName.from(domain), true);
|
||||
} 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();
|
||||
}
|
||||
}
|
||||
|
||||
private static List<Result> fromIpAddress(String domain) {
|
||||
if (!IP.matches(domain)) {
|
||||
return Collections.emptyList();
|
||||
return Futures.immediateFuture(results);
|
||||
}
|
||||
},
|
||||
MoreExecutors.directExecutor());
|
||||
final var orderedFuture =
|
||||
Futures.transform(
|
||||
combinedWithFallback,
|
||||
all -> Ordering.from(RESULT_COMPARATOR).immutableSortedCopy(all),
|
||||
MoreExecutors.directExecutor());
|
||||
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) {
|
||||
final var ordered = orderedFuture.get();
|
||||
Log.d(Config.LOGTAG, "Resolver (" + ordered.size() + "): " + ordered);
|
||||
return ordered;
|
||||
} catch (final ExecutionException e) {
|
||||
Log.d(Config.LOGTAG, "error resolving DNS", e);
|
||||
return Collections.emptyList();
|
||||
} catch (final InterruptedException e) {
|
||||
Log.d(Config.LOGTAG, "DNS resolution interrupted");
|
||||
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) {
|
||||
private static List<Result> fromIpAddress(final String domain) {
|
||||
if (IP.matches(domain)) {
|
||||
final InetAddress inetAddress;
|
||||
try {
|
||||
inetAddress = InetAddress.getByName(domain);
|
||||
} catch (final UnknownHostException e) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
final Result result = new Result();
|
||||
result.ip = inetAddress;
|
||||
result.port = DEFAULT_PORT_XMPP;
|
||||
return Collections.singletonList(result);
|
||||
} else {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
private static ListenableFuture<List<Result>> resolveSrvAsFuture(
|
||||
final String domain, final boolean directTls) {
|
||||
final DnsName dnsName =
|
||||
DnsName.from(
|
||||
(directTls ? DIRECT_TLS_SERVICE : STARTTLS_SERVICE) + "._tcp." + domain);
|
||||
final var resultFuture = resolveAsFuture(dnsName, SRV.class);
|
||||
return Futures.transformAsync(
|
||||
resultFuture,
|
||||
result -> resolveIpsAsFuture(result, directTls),
|
||||
MoreExecutors.directExecutor());
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private static ListenableFuture<List<Result>> resolveIpsAsFuture(
|
||||
final ResolverResult<SRV> srvResolverResult, final boolean directTls) {
|
||||
final ImmutableList.Builder<ListenableFuture<List<Result>>> futuresBuilder =
|
||||
new ImmutableList.Builder<>();
|
||||
for (final SRV record : srvResolverResult.getAnswersOrEmptySet()) {
|
||||
if (record.target.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 {
|
||||
thread.join();
|
||||
} catch (InterruptedException e) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
}
|
||||
final var ipv4sRaw =
|
||||
resolveIpsAsFuture(
|
||||
record, A.class, srvResolverResult.isAuthenticData(), directTls);
|
||||
final var ipv4s =
|
||||
Futures.transform(
|
||||
ipv4sRaw,
|
||||
results -> {
|
||||
if (results.isEmpty()) {
|
||||
final Result resolverResult =
|
||||
Result.fromRecord(record, directTls);
|
||||
resolverResult.authenticated =
|
||||
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 <D extends InternetAddressRR> List<Result> resolveIp(SRV srv, Class<D> type, boolean authenticated, boolean directTls) {
|
||||
List<Result> list = new ArrayList<>();
|
||||
try {
|
||||
ResolverResult<D> results = resolveWithFallback(srv.target, type);
|
||||
for (D record : results.getAnswersOrEmptySet()) {
|
||||
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();
|
||||
},
|
||||
MoreExecutors.directExecutor());
|
||||
}
|
||||
|
||||
private static <D extends InternetAddressRR<?>>
|
||||
ListenableFuture<List<Result>> resolveIpsAsFuture(
|
||||
final SRV srv, Class<D> type, boolean authenticated, boolean directTls) {
|
||||
final var resultFuture = resolveAsFuture(srv.target, type);
|
||||
return Futures.transform(
|
||||
resultFuture,
|
||||
result -> {
|
||||
final var builder = new ImmutableList.Builder<Result>();
|
||||
for (D record : result.getAnswersOrEmptySet()) {
|
||||
Result resolverResult = Result.fromRecord(srv, directTls);
|
||||
resolverResult.authenticated = results.isAuthenticData() && authenticated;
|
||||
resolverResult.authenticated =
|
||||
result.isAuthenticData()
|
||||
&& authenticated; // TODO technically it does not matter if
|
||||
// the IP
|
||||
// was authenticated
|
||||
resolverResult.ip = record.getInetAddress();
|
||||
list.add(resolverResult);
|
||||
builder.add(resolverResult);
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": error resolving " + type.getSimpleName() + " " + t.getMessage());
|
||||
}
|
||||
return list;
|
||||
return builder.build();
|
||||
},
|
||||
MoreExecutors.directExecutor());
|
||||
}
|
||||
|
||||
private static List<Result> resolveNoSrvRecords(DnsName dnsName, boolean withCnames) {
|
||||
final List<Result> results = new ArrayList<>();
|
||||
try {
|
||||
ResolverResult<A> aResult = resolveWithFallback(dnsName, A.class);
|
||||
for (A a : aResult.getAnswersOrEmptySet()) {
|
||||
Result r = Result.createDefault(dnsName, a.getInetAddress());
|
||||
r.authenticated = aResult.isAuthenticData();
|
||||
results.add(r);
|
||||
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);
|
||||
}
|
||||
ResolverResult<AAAA> aaaaResult = resolveWithFallback(dnsName, AAAA.class);
|
||||
for (AAAA aaaa : aaaaResult.getAnswersOrEmptySet()) {
|
||||
Result r = Result.createDefault(dnsName, aaaa.getInetAddress());
|
||||
r.authenticated = aaaaResult.isAuthenticData();
|
||||
results.add(r);
|
||||
}
|
||||
if (results.size() == 0 && withCnames) {
|
||||
ResolverResult<CNAME> cnameResult = resolveWithFallback(dnsName, CNAME.class);
|
||||
for (CNAME cname : cnameResult.getAnswersOrEmptySet()) {
|
||||
for (Result r : resolveNoSrvRecords(cname.name, false)) {
|
||||
r.authenticated = r.authenticated && cnameResult.isAuthenticData();
|
||||
results.add(r);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (final Throwable throwable) {
|
||||
if (!(Throwables.getRootCause(throwable) instanceof InterruptedException)) {
|
||||
Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + "error resolving fallback records", throwable);
|
||||
}
|
||||
}
|
||||
results.add(Result.createDefault(dnsName));
|
||||
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> ResolverResult<D> resolveWithFallback(DnsName dnsName, Class<D> type) throws IOException {
|
||||
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 {
|
||||
|
@ -421,9 +501,11 @@ public class Resolver {
|
|||
}
|
||||
}
|
||||
return ResolverApi.INSTANCE.resolve(question);
|
||||
},
|
||||
DNS_QUERY_EXECUTOR);
|
||||
}
|
||||
|
||||
public static class Result implements Comparable<Result> {
|
||||
public static class Result {
|
||||
public static final String DOMAIN = "domain";
|
||||
public static final String IP = "ip";
|
||||
public static final String HOSTNAME = "hostname";
|
||||
|
@ -438,40 +520,42 @@ public class Resolver {
|
|||
private boolean authenticated = false;
|
||||
private int priority;
|
||||
|
||||
static Result fromRecord(SRV srv, boolean directTls) {
|
||||
Result result = new Result();
|
||||
static Result fromRecord(final SRV srv, final boolean directTls) {
|
||||
final Result result = new Result();
|
||||
result.port = srv.port;
|
||||
result.hostname = srv.name;
|
||||
result.hostname = srv.target;
|
||||
result.directTls = directTls;
|
||||
result.priority = srv.priority;
|
||||
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.port = DEFAULT_PORT_XMPP;
|
||||
result.hostname = hostname;
|
||||
result.ip = ip;
|
||||
result.authenticated = authenticated;
|
||||
return result;
|
||||
}
|
||||
|
||||
static Result createDefault(DnsName hostname) {
|
||||
return createDefault(hostname, null);
|
||||
static Result createDefault(final DnsName hostname) {
|
||||
return createDefault(hostname, null, false);
|
||||
}
|
||||
|
||||
public static Result fromCursor(Cursor cursor) {
|
||||
public static Result fromCursor(final Cursor cursor) {
|
||||
final Result result = new Result();
|
||||
try {
|
||||
result.ip = InetAddress.getByAddress(cursor.getBlob(cursor.getColumnIndex(IP)));
|
||||
} catch (UnknownHostException e) {
|
||||
result.ip =
|
||||
InetAddress.getByAddress(cursor.getBlob(cursor.getColumnIndexOrThrow(IP)));
|
||||
} catch (final UnknownHostException e) {
|
||||
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.port = cursor.getInt(cursor.getColumnIndex(PORT));
|
||||
result.priority = cursor.getInt(cursor.getColumnIndex(PRIORITY));
|
||||
result.authenticated = cursor.getInt(cursor.getColumnIndex(AUTHENTICATED)) > 0;
|
||||
result.directTls = cursor.getInt(cursor.getColumnIndex(DIRECT_TLS)) > 0;
|
||||
result.port = cursor.getInt(cursor.getColumnIndexOrThrow(PORT));
|
||||
result.priority = cursor.getInt(cursor.getColumnIndexOrThrow(PRIORITY));
|
||||
result.authenticated = cursor.getInt(cursor.getColumnIndexOrThrow(AUTHENTICATED)) > 0;
|
||||
result.directTls = cursor.getInt(cursor.getColumnIndexOrThrow(DIRECT_TLS)) > 0;
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -479,26 +563,18 @@ public class Resolver {
|
|||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
|
||||
Result result = (Result) o;
|
||||
|
||||
if (port != result.port) return false;
|
||||
if (directTls != result.directTls) return false;
|
||||
if (authenticated != result.authenticated) return false;
|
||||
if (priority != result.priority) return false;
|
||||
if (ip != null ? !ip.equals(result.ip) : result.ip != null) return false;
|
||||
return hostname != null ? hostname.equals(result.hostname) : result.hostname == null;
|
||||
return port == result.port
|
||||
&& directTls == result.directTls
|
||||
&& authenticated == result.authenticated
|
||||
&& priority == result.priority
|
||||
&& Objects.equal(ip, result.ip)
|
||||
&& Objects.equal(hostname, result.hostname);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = ip != null ? ip.hashCode() : 0;
|
||||
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;
|
||||
return Objects.hashCode(ip, hostname, port, directTls, authenticated, priority);
|
||||
}
|
||||
|
||||
public InetAddress getIp() {
|
||||
|
@ -522,38 +598,16 @@ public class Resolver {
|
|||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public String toString() {
|
||||
return "Result{" +
|
||||
"ip='" + (ip == null ? null : ip.getHostAddress()) + '\'' +
|
||||
", hostame='" + (hostname == null ? null : hostname.toString()) + '\'' +
|
||||
", port=" + port +
|
||||
", directTls=" + directTls +
|
||||
", authenticated=" + authenticated +
|
||||
", priority=" + priority +
|
||||
'}';
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
return MoreObjects.toStringHelper(this)
|
||||
.add("ip", ip)
|
||||
.add("hostname", hostname)
|
||||
.add("port", port)
|
||||
.add("directTls", directTls)
|
||||
.add("authenticated", authenticated)
|
||||
.add("priority", priority)
|
||||
.toString();
|
||||
}
|
||||
|
||||
public ContentValues toContentValues() {
|
||||
|
@ -626,5 +680,4 @@ public class Resolver {
|
|||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ import android.util.Log;
|
|||
import androidx.annotation.NonNull;
|
||||
import androidx.core.app.NotificationCompat;
|
||||
import androidx.work.ForegroundInfo;
|
||||
import androidx.work.WorkManager;
|
||||
import androidx.work.Worker;
|
||||
import androidx.work.WorkerParameters;
|
||||
|
||||
|
@ -35,7 +36,6 @@ import eu.siacs.conversations.entities.Conversation;
|
|||
import eu.siacs.conversations.entities.Message;
|
||||
import eu.siacs.conversations.persistance.DatabaseBackend;
|
||||
import eu.siacs.conversations.persistance.FileBackend;
|
||||
import eu.siacs.conversations.receiver.WorkManagerEventReceiver;
|
||||
import eu.siacs.conversations.utils.BackupFileHeader;
|
||||
import eu.siacs.conversations.utils.Compatibility;
|
||||
|
||||
|
@ -99,6 +99,7 @@ public class ExportBackupWorker extends Worker {
|
|||
@NonNull
|
||||
@Override
|
||||
public Result doWork() {
|
||||
setForegroundAsync(getForegroundInfo());
|
||||
final List<File> files;
|
||||
try {
|
||||
files = export();
|
||||
|
@ -227,18 +228,14 @@ public class ExportBackupWorker extends Worker {
|
|||
IV,
|
||||
salt);
|
||||
final var notification = getNotification();
|
||||
if (!recurringBackup) {
|
||||
final var cancel = new Intent(context, WorkManagerEventReceiver.class);
|
||||
cancel.setAction(WorkManagerEventReceiver.ACTION_STOP_BACKUP);
|
||||
final var cancelPendingIntent =
|
||||
PendingIntent.getBroadcast(context, 197, cancel, PENDING_INTENT_FLAGS);
|
||||
WorkManager.getInstance(context).createCancelPendingIntent(getId());
|
||||
notification.addAction(
|
||||
new NotificationCompat.Action.Builder(
|
||||
R.drawable.ic_cancel_24dp,
|
||||
context.getString(R.string.cancel),
|
||||
cancelPendingIntent)
|
||||
.build());
|
||||
}
|
||||
final Progress progress = new Progress(notification, max, count);
|
||||
final File directory = file.getParentFile();
|
||||
if (directory != null && directory.mkdirs()) {
|
||||
|
|
|
@ -5,7 +5,9 @@ import androidx.annotation.NonNull;
|
|||
import com.google.common.base.Optional;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.primitives.Ints;
|
||||
import com.google.common.primitives.Longs;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
@ -16,7 +18,7 @@ import java.util.stream.Collectors;
|
|||
import eu.siacs.conversations.utils.XmlHelper;
|
||||
import eu.siacs.conversations.xmpp.InvalidJid;
|
||||
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 {
|
||||
private final String name;
|
||||
|
@ -141,6 +143,10 @@ public class Element implements Node {
|
|||
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
|
||||
public Element setChildren(List<Element> 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());
|
||||
}
|
||||
|
||||
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) {
|
||||
if (name != null && value != null) {
|
||||
this.attributes.put(name, value);
|
||||
|
@ -224,7 +255,7 @@ public class Element implements Node {
|
|||
return result;
|
||||
}
|
||||
|
||||
public Element removeAttribute(String name) {
|
||||
public Element removeAttribute(final String name) {
|
||||
this.attributes.remove(name);
|
||||
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() {
|
||||
return this.attributes;
|
||||
}
|
||||
|
|
|
@ -37,7 +37,7 @@ public class LocalizedContent {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (contents.size() == 0) {
|
||||
if (contents.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
final String userLanguage = Locale.getDefault().getLanguage();
|
||||
|
|
|
@ -1,8 +1,29 @@
|
|||
package eu.siacs.conversations.xml;
|
||||
|
||||
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 STANZAS = "urn:ietf:params:xml:ns:xmpp-stanzas";
|
||||
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_INFO = "http://jabber.org/protocol/disco#info";
|
||||
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 TLS = "urn:ietf:params:xml:ns:xmpp-tls";
|
||||
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_CONFIG_NODE_MAX = PUBSUB + "#config-node-max";
|
||||
public static final String PUBSUB_ERROR = PUBSUB + "#errors";
|
||||
public static final String PUBSUB_OWNER = PUBSUB + "#owner";
|
||||
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 BIND2 = "urn:xmpp:bind:0";
|
||||
public static final String STREAM_MANAGEMENT = "urn:xmpp:sm:3";
|
||||
|
@ -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_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_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_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_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_RTP_HEADER_EXTENSIONS = "urn:xmpp:jingle:apps:rtp:rtp-hdrext: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 JINGLE_RTP_HEADER_EXTENSIONS =
|
||||
"urn:xmpp:jingle:apps:rtp:rtp-hdrext: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 PING = "urn:xmpp:ping";
|
||||
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 PARS = "urn:xmpp:pars:0";
|
||||
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 JINGLE_TRANSPORT_ICE_OPTION = "http://gultsch.de/xmpp/drafts/jingle/transports/ice-udp/option";
|
||||
public static final String OMEMO_DTLS_SRTP_VERIFICATION =
|
||||
"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 VCARD4 = "urn:ietf:params:xml:ns:vcard-4.0";
|
||||
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 MDS_DISPLAYED = "urn:xmpp:mds:displayed: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.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.XmlPullParserException;
|
||||
|
||||
|
@ -11,8 +17,6 @@ import java.io.IOException;
|
|||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
import eu.siacs.conversations.Config;
|
||||
|
||||
public class XmlReader implements Closeable {
|
||||
private final XmlPullParser parser;
|
||||
private InputStream is;
|
||||
|
@ -90,8 +94,21 @@ public class XmlReader implements Closeable {
|
|||
return null;
|
||||
}
|
||||
|
||||
public Element readElement(Tag currentTag) throws IOException {
|
||||
Element element = new Element(currentTag.getName());
|
||||
public <T extends StreamElement> T readElement(final Tag current, final Class<T> clazz)
|
||||
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());
|
||||
Tag nextTag = this.readTag();
|
||||
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);
|
||||
}
|
||||
|
||||
public void submit(Bundle options) {
|
||||
for (Field field : getFields()) {
|
||||
public void submit(final Bundle options) {
|
||||
for (final Field field : getFields()) {
|
||||
if (options.containsKey(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.jingle.stanzas.Content;
|
||||
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.Reason;
|
||||
import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription;
|
||||
import eu.siacs.conversations.xmpp.jingle.transports.InbandBytestreamsTransport;
|
||||
import eu.siacs.conversations.xmpp.jingle.transports.Transport;
|
||||
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 java.lang.ref.WeakReference;
|
||||
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);
|
||||
}
|
||||
|
||||
public void deliverPacket(final Account account, final JinglePacket packet) {
|
||||
final String sessionId = packet.getSessionId();
|
||||
final JinglePacket.Action action = packet.getAction();
|
||||
public void deliverPacket(final Account account, final Iq packet) {
|
||||
final var jingle = packet.getExtension(Jingle.class);
|
||||
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) {
|
||||
respondWithJingleError(account, packet, "unknown-session", "item-not-found", "cancel");
|
||||
return;
|
||||
|
@ -88,13 +89,13 @@ public class JingleConnectionManager extends AbstractConnectionManager {
|
|||
respondWithJingleError(account, packet, null, "bad-request", "cancel");
|
||||
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);
|
||||
if (existingJingleConnection != null) {
|
||||
existingJingleConnection.deliverPacket(packet);
|
||||
} else if (action == JinglePacket.Action.SESSION_INITIATE) {
|
||||
} else if (action == Jingle.Action.SESSION_INITIATE) {
|
||||
final Jid from = packet.getFrom();
|
||||
final Content content = packet.getJingleContent();
|
||||
final Content content = jingle.getJingleContent();
|
||||
final String descriptionNamespace =
|
||||
content == null ? null : content.getDescriptionNamespace();
|
||||
final AbstractJingleConnection connection;
|
||||
|
@ -165,14 +166,14 @@ public class JingleConnectionManager extends AbstractConnectionManager {
|
|||
}
|
||||
|
||||
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(
|
||||
account, request.generateResponse(IqPacket.TYPE.RESULT), null);
|
||||
final JinglePacket sessionTermination =
|
||||
new JinglePacket(JinglePacket.Action.SESSION_TERMINATE, id.sessionId);
|
||||
sessionTermination.setTo(id.with);
|
||||
account, request.generateResponse(Iq.Type.RESULT), null);
|
||||
final var iq = new Iq(Iq.Type.SET);
|
||||
iq.setTo(id.with);
|
||||
final var sessionTermination = iq.addExtension(new Jingle(Jingle.Action.SESSION_TERMINATE, id.sessionId));
|
||||
sessionTermination.setReason(Reason.BUSY, null);
|
||||
mXmppConnectionService.sendIqPacket(account, sessionTermination, null);
|
||||
mXmppConnectionService.sendIqPacket(account, iq, null);
|
||||
}
|
||||
|
||||
private boolean isUsingClearNet(final Account account) {
|
||||
|
@ -265,11 +266,11 @@ public class JingleConnectionManager extends AbstractConnectionManager {
|
|||
|
||||
void respondWithJingleError(
|
||||
final Account account,
|
||||
final IqPacket original,
|
||||
final Iq original,
|
||||
final String jingleCondition,
|
||||
final String condition,
|
||||
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");
|
||||
error.setAttribute("type", conditionType);
|
||||
error.addChild(condition, "urn:ietf:params:xml:ns:xmpp-stanzas");
|
||||
|
@ -440,7 +441,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
|
|||
final int activeDevices = account.activeDevicesWithRtpCapability();
|
||||
Log.d(Config.LOGTAG, "active devices with rtp capability: " + activeDevices);
|
||||
if (activeDevices == 0) {
|
||||
final MessagePacket reject =
|
||||
final var reject =
|
||||
mXmppConnectionService
|
||||
.getMessageGenerator()
|
||||
.sessionReject(from, sessionId);
|
||||
|
@ -494,10 +495,11 @@ public class JingleConnectionManager extends AbstractConnectionManager {
|
|||
if (remoteMsgId == null) {
|
||||
return;
|
||||
}
|
||||
final MessagePacket errorMessage = new MessagePacket();
|
||||
final var errorMessage =
|
||||
new im.conversations.android.xmpp.model.stanza.Message();
|
||||
errorMessage.setTo(from);
|
||||
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");
|
||||
error.setAttribute("code", "404");
|
||||
error.setAttribute("type", "cancel");
|
||||
|
@ -722,7 +724,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
|
|||
rtpSessionProposal.sessionId,
|
||||
RtpEndUserState.RETRACTED);
|
||||
}
|
||||
final MessagePacket messagePacket =
|
||||
final var messagePacket =
|
||||
mXmppConnectionService.getMessageGenerator().sessionRetract(rtpSessionProposal);
|
||||
writeLogMissedOutgoing(
|
||||
account,
|
||||
|
@ -791,7 +793,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
|
|||
this.rtpSessionProposals.put(proposal, DeviceDiscoveryState.SEARCHING);
|
||||
mXmppConnectionService.notifyJingleRtpConnectionUpdate(
|
||||
account, proposal.with, proposal.sessionId, RtpEndUserState.FINDING_DEVICE);
|
||||
final MessagePacket messagePacket =
|
||||
final var messagePacket =
|
||||
mXmppConnectionService.getMessageGenerator().sessionProposal(proposal);
|
||||
mXmppConnectionService.sendMessagePacket(account, messagePacket);
|
||||
return proposal;
|
||||
|
@ -801,7 +803,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
|
|||
public void sendJingleMessageFinish(
|
||||
final Contact contact, final String sessionId, final Reason reason) {
|
||||
final var account = contact.getAccount();
|
||||
final MessagePacket messagePacket =
|
||||
final var messagePacket =
|
||||
mXmppConnectionService
|
||||
.getMessageGenerator()
|
||||
.sessionFinish(contact.getJid(), sessionId, reason);
|
||||
|
@ -843,7 +845,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
|
|||
return false;
|
||||
}
|
||||
|
||||
public void deliverIbbPacket(final Account account, final IqPacket packet) {
|
||||
public void deliverIbbPacket(final Account account, final Iq packet) {
|
||||
final String sid;
|
||||
final Element payload;
|
||||
final InbandBytestreamsTransport.PacketType packetType;
|
||||
|
@ -869,7 +871,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
|
|||
Config.LOGTAG,
|
||||
account.getJid().asBareJid() + ": unable to deliver ibb packet. missing sid");
|
||||
account.getXmppConnection()
|
||||
.sendIqPacket(packet.generateResponse(IqPacket.TYPE.ERROR), null);
|
||||
.sendIqPacket(packet.generateResponse(Iq.Type.ERROR), null);
|
||||
return;
|
||||
}
|
||||
for (final AbstractJingleConnection connection : this.connections.values()) {
|
||||
|
@ -880,11 +882,11 @@ public class JingleConnectionManager extends AbstractConnectionManager {
|
|||
if (inBandTransport.deliverPacket(packetType, packet.getFrom(), payload)) {
|
||||
account.getXmppConnection()
|
||||
.sendIqPacket(
|
||||
packet.generateResponse(IqPacket.TYPE.RESULT), null);
|
||||
packet.generateResponse(Iq.Type.RESULT), null);
|
||||
} else {
|
||||
account.getXmppConnection()
|
||||
.sendIqPacket(
|
||||
packet.generateResponse(IqPacket.TYPE.ERROR), null);
|
||||
packet.generateResponse(Iq.Type.ERROR), null);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -895,7 +897,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
|
|||
Config.LOGTAG,
|
||||
account.getJid().asBareJid() + ": unable to deliver ibb packet with sid=" + sid);
|
||||
account.getXmppConnection()
|
||||
.sendIqPacket(packet.generateResponse(IqPacket.TYPE.ERROR), null);
|
||||
.sendIqPacket(packet.generateResponse(Iq.Type.ERROR), null);
|
||||
}
|
||||
|
||||
public void notifyRebound(final Account account) {
|
||||
|
@ -946,7 +948,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
|
|||
account.getJid().asBareJid()
|
||||
+ ": resending session proposal to "
|
||||
+ proposal.with);
|
||||
final MessagePacket messagePacket =
|
||||
final var messagePacket =
|
||||
mXmppConnectionService.getMessageGenerator().sessionProposal(proposal);
|
||||
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.Group;
|
||||
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.Propose;
|
||||
import eu.siacs.conversations.xmpp.jingle.stanzas.Reason;
|
||||
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.EglBase;
|
||||
|
@ -145,24 +145,25 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
|||
}
|
||||
|
||||
@Override
|
||||
synchronized void deliverPacket(final JinglePacket jinglePacket) {
|
||||
switch (jinglePacket.getAction()) {
|
||||
case SESSION_INITIATE -> receiveSessionInitiate(jinglePacket);
|
||||
case TRANSPORT_INFO -> receiveTransportInfo(jinglePacket);
|
||||
case SESSION_ACCEPT -> receiveSessionAccept(jinglePacket);
|
||||
case SESSION_TERMINATE -> receiveSessionTerminate(jinglePacket);
|
||||
case CONTENT_ADD -> receiveContentAdd(jinglePacket);
|
||||
case CONTENT_ACCEPT -> receiveContentAccept(jinglePacket);
|
||||
case CONTENT_REJECT -> receiveContentReject(jinglePacket);
|
||||
case CONTENT_REMOVE -> receiveContentRemove(jinglePacket);
|
||||
case CONTENT_MODIFY -> receiveContentModify(jinglePacket);
|
||||
synchronized void deliverPacket(final Iq iq) {
|
||||
final var jingle = iq.getExtension(Jingle.class);
|
||||
switch (jingle.getAction()) {
|
||||
case SESSION_INITIATE -> receiveSessionInitiate(iq, jingle);
|
||||
case TRANSPORT_INFO -> receiveTransportInfo(iq, jingle);
|
||||
case SESSION_ACCEPT -> receiveSessionAccept(iq, jingle);
|
||||
case SESSION_TERMINATE -> receiveSessionTerminate(iq);
|
||||
case CONTENT_ADD -> receiveContentAdd(iq, jingle);
|
||||
case CONTENT_ACCEPT -> receiveContentAccept(iq);
|
||||
case CONTENT_REJECT -> receiveContentReject(iq, jingle);
|
||||
case CONTENT_REMOVE -> receiveContentRemove(iq, jingle);
|
||||
case CONTENT_MODIFY -> receiveContentModify(iq, jingle);
|
||||
default -> {
|
||||
respondOk(jinglePacket);
|
||||
respondOk(iq);
|
||||
Log.d(
|
||||
Config.LOGTAG,
|
||||
String.format(
|
||||
"%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);
|
||||
}
|
||||
|
||||
private void receiveSessionTerminate(final JinglePacket jinglePacket) {
|
||||
private void receiveSessionTerminate(final Iq 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;
|
||||
Log.d(
|
||||
Config.LOGTAG,
|
||||
|
@ -224,7 +226,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
|||
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
|
||||
// INITIALIZED only after transport-info has been received
|
||||
if (isInState(
|
||||
|
@ -235,7 +237,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
|||
State.SESSION_ACCEPTED)) {
|
||||
final RtpContentMap contentMap;
|
||||
try {
|
||||
contentMap = RtpContentMap.of(jinglePacket);
|
||||
contentMap = RtpContentMap.of(jingle);
|
||||
} catch (final IllegalArgumentException | NullPointerException e) {
|
||||
Log.d(
|
||||
Config.LOGTAG,
|
||||
|
@ -265,7 +267,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
|||
}
|
||||
|
||||
private void receiveTransportInfo(
|
||||
final JinglePacket jinglePacket, final RtpContentMap contentMap) {
|
||||
final Iq jinglePacket, final RtpContentMap contentMap) {
|
||||
final Set<Map.Entry<String, DescriptionTransport<RtpDescription, IceUdpTransportInfo>>>
|
||||
candidates = contentMap.contents.entrySet();
|
||||
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;
|
||||
try {
|
||||
modification = RtpContentMap.of(jinglePacket);
|
||||
modification = RtpContentMap.of(jingle);
|
||||
modification.requireContentDescriptions();
|
||||
} catch (final RuntimeException e) {
|
||||
Log.d(
|
||||
Config.LOGTAG,
|
||||
id.getAccount().getJid().asBareJid() + ": improperly formatted contents",
|
||||
Throwables.getRootCause(e));
|
||||
respondOk(jinglePacket);
|
||||
respondOk(iq);
|
||||
webRTCWrapper.close();
|
||||
sendSessionTerminate(Reason.of(e), e.getMessage());
|
||||
return;
|
||||
|
@ -330,12 +332,12 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
|||
new FutureCallback<>() {
|
||||
@Override
|
||||
public void onSuccess(final RtpContentMap rtpContentMap) {
|
||||
receiveContentAdd(jinglePacket, rtpContentMap);
|
||||
receiveContentAdd(iq, rtpContentMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(@NonNull Throwable throwable) {
|
||||
respondOk(jinglePacket);
|
||||
respondOk(iq);
|
||||
final Throwable rootCause = Throwables.getRootCause(throwable);
|
||||
Log.d(
|
||||
Config.LOGTAG,
|
||||
|
@ -349,12 +351,12 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
|||
},
|
||||
MoreExecutors.directExecutor());
|
||||
} else {
|
||||
terminateWithOutOfOrder(jinglePacket);
|
||||
terminateWithOutOfOrder(iq);
|
||||
}
|
||||
}
|
||||
|
||||
private void receiveContentAdd(
|
||||
final JinglePacket jinglePacket, final RtpContentMap modification) {
|
||||
final Iq jinglePacket, final RtpContentMap modification) {
|
||||
final RtpContentMap remote = getRemoteContentMap();
|
||||
if (!Collections.disjoint(modification.getNames(), remote.getNames())) {
|
||||
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;
|
||||
try {
|
||||
receivedContentAccept = RtpContentMap.of(jinglePacket);
|
||||
receivedContentAccept = RtpContentMap.of(jingle);
|
||||
receivedContentAccept.requireContentDescriptions();
|
||||
} catch (final RuntimeException e) {
|
||||
Log.d(
|
||||
|
@ -494,14 +497,14 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
|||
updateEndUserState();
|
||||
}
|
||||
|
||||
private void receiveContentModify(final JinglePacket jinglePacket) {
|
||||
private void receiveContentModify(final Iq jinglePacket, final Jingle jingle) {
|
||||
if (this.state != State.SESSION_ACCEPTED) {
|
||||
terminateWithOutOfOrder(jinglePacket);
|
||||
return;
|
||||
}
|
||||
final Map<String, Content.Senders> modification =
|
||||
Maps.transformEntries(
|
||||
jinglePacket.getJingleContents(), (key, value) -> value.getSenders());
|
||||
jingle.getJingleContents(), (key, value) -> value.getSenders());
|
||||
final boolean isInitiator = isInitiator();
|
||||
final RtpContentMap currentOutgoing = this.outgoingContentAdd;
|
||||
final RtpContentMap remoteContentMap = this.getRemoteContentMap();
|
||||
|
@ -604,10 +607,10 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
|||
return candidateBuilder.build();
|
||||
}
|
||||
|
||||
private void receiveContentReject(final JinglePacket jinglePacket) {
|
||||
private void receiveContentReject(final Iq jinglePacket, final Jingle jingle) {
|
||||
final RtpContentMap receivedContentReject;
|
||||
try {
|
||||
receivedContentReject = RtpContentMap.of(jinglePacket);
|
||||
receivedContentReject = RtpContentMap.of(jingle);
|
||||
} catch (final RuntimeException e) {
|
||||
Log.d(
|
||||
Config.LOGTAG,
|
||||
|
@ -660,10 +663,10 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
|||
+ summary);
|
||||
}
|
||||
|
||||
private void receiveContentRemove(final JinglePacket jinglePacket) {
|
||||
private void receiveContentRemove(final Iq jinglePacket, final Jingle jingle) {
|
||||
final RtpContentMap receivedContentRemove;
|
||||
try {
|
||||
receivedContentRemove = RtpContentMap.of(jinglePacket);
|
||||
receivedContentRemove = RtpContentMap.of(jingle);
|
||||
receivedContentRemove.requireContentDescriptions();
|
||||
} catch (final RuntimeException e) {
|
||||
Log.d(
|
||||
|
@ -697,8 +700,8 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
|||
String.format(
|
||||
"%s only supports %s as a means to retract a not yet accepted %s",
|
||||
BuildConfig.APP_NAME,
|
||||
JinglePacket.Action.CONTENT_REMOVE,
|
||||
JinglePacket.Action.CONTENT_ADD));
|
||||
Jingle.Action.CONTENT_REMOVE,
|
||||
Jingle.Action.CONTENT_ADD));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -723,10 +726,10 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
|||
return;
|
||||
}
|
||||
this.outgoingContentAdd = null;
|
||||
final JinglePacket retract =
|
||||
final Iq retract =
|
||||
outgoingContentAdd
|
||||
.toStub()
|
||||
.toJinglePacket(JinglePacket.Action.CONTENT_REMOVE, id.sessionId);
|
||||
.toJinglePacket(Jingle.Action.CONTENT_REMOVE, id.sessionId);
|
||||
this.send(retract);
|
||||
Log.d(
|
||||
Config.LOGTAG,
|
||||
|
@ -782,16 +785,16 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
|||
"content addition is receive only. we want to upgrade to 'both'");
|
||||
final RtpContentMap modifiedSenders =
|
||||
incomingContentAdd.modifiedSenders(Content.Senders.BOTH);
|
||||
final JinglePacket proposedContentModification =
|
||||
final Iq proposedContentModification =
|
||||
modifiedSenders
|
||||
.toStub()
|
||||
.toJinglePacket(JinglePacket.Action.CONTENT_MODIFY, id.sessionId);
|
||||
.toJinglePacket(Jingle.Action.CONTENT_MODIFY, id.sessionId);
|
||||
proposedContentModification.setTo(id.with);
|
||||
xmppConnectionService.sendIqPacket(
|
||||
id.account,
|
||||
proposedContentModification,
|
||||
(account, response) -> {
|
||||
if (response.getType() == IqPacket.TYPE.RESULT) {
|
||||
(response) -> {
|
||||
if (response.getType() == Iq.Type.RESULT) {
|
||||
Log.d(
|
||||
Config.LOGTAG,
|
||||
id.account.getJid().asBareJid()
|
||||
|
@ -885,7 +888,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
|||
|
||||
@Override
|
||||
public void onFailure(@NonNull final Throwable throwable) {
|
||||
failureToPerformAction(JinglePacket.Action.CONTENT_ACCEPT, throwable);
|
||||
failureToPerformAction(Jingle.Action.CONTENT_ACCEPT, throwable);
|
||||
}
|
||||
},
|
||||
MoreExecutors.directExecutor());
|
||||
|
@ -897,9 +900,9 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
|||
}
|
||||
|
||||
private void sendContentAccept(final RtpContentMap contentAcceptMap) {
|
||||
final JinglePacket jinglePacket =
|
||||
contentAcceptMap.toJinglePacket(JinglePacket.Action.CONTENT_ACCEPT, id.sessionId);
|
||||
send(jinglePacket);
|
||||
final Iq iq =
|
||||
contentAcceptMap.toJinglePacket(Jingle.Action.CONTENT_ACCEPT, id.sessionId);
|
||||
send(iq);
|
||||
}
|
||||
|
||||
public synchronized void rejectContentAdd() {
|
||||
|
@ -913,20 +916,20 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
|||
}
|
||||
|
||||
private void rejectContentAdd(final RtpContentMap contentMap) {
|
||||
final JinglePacket jinglePacket =
|
||||
final Iq iq =
|
||||
contentMap
|
||||
.toStub()
|
||||
.toJinglePacket(JinglePacket.Action.CONTENT_REJECT, id.sessionId);
|
||||
.toJinglePacket(Jingle.Action.CONTENT_REJECT, id.sessionId);
|
||||
Log.d(
|
||||
Config.LOGTAG,
|
||||
id.getAccount().getJid().asBareJid()
|
||||
+ ": rejecting content "
|
||||
+ ContentAddition.summary(contentMap));
|
||||
send(jinglePacket);
|
||||
send(iq);
|
||||
}
|
||||
|
||||
private boolean checkForIceRestart(
|
||||
final JinglePacket jinglePacket, final RtpContentMap rtpContentMap) {
|
||||
final Iq jinglePacket, final RtpContentMap rtpContentMap) {
|
||||
final RtpContentMap existing = getRemoteContentMap();
|
||||
final Set<IceUdpTransportInfo.Credentials> existingCredentials;
|
||||
final IceUdpTransportInfo.Credentials newCredentials;
|
||||
|
@ -1005,7 +1008,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
|||
}
|
||||
|
||||
private boolean applyIceRestart(
|
||||
final JinglePacket jinglePacket,
|
||||
final Iq jinglePacket,
|
||||
final RtpContentMap restartContentMap,
|
||||
final boolean isOffer)
|
||||
throws ExecutionException, InterruptedException {
|
||||
|
@ -1106,7 +1109,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
|||
}
|
||||
|
||||
private ListenableFuture<RtpContentMap> receiveRtpContentMap(
|
||||
final JinglePacket jinglePacket, final boolean expectVerification) {
|
||||
final Jingle jinglePacket, final boolean expectVerification) {
|
||||
try {
|
||||
return receiveRtpContentMap(RtpContentMap.of(jinglePacket), expectVerification);
|
||||
} 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()) {
|
||||
receiveOutOfOrderAction(jinglePacket, JinglePacket.Action.SESSION_INITIATE);
|
||||
receiveOutOfOrderAction(jinglePacket, Jingle.Action.SESSION_INITIATE);
|
||||
return;
|
||||
}
|
||||
final ListenableFuture<RtpContentMap> future = receiveRtpContentMap(jinglePacket, false);
|
||||
final ListenableFuture<RtpContentMap> future = receiveRtpContentMap(jingle, false);
|
||||
Futures.addCallback(
|
||||
future,
|
||||
new FutureCallback<>() {
|
||||
|
@ -1173,7 +1176,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
|||
}
|
||||
|
||||
private void receiveSessionInitiate(
|
||||
final JinglePacket jinglePacket, final RtpContentMap contentMap) {
|
||||
final Iq jinglePacket, final RtpContentMap contentMap) {
|
||||
try {
|
||||
contentMap.requireContentDescriptions();
|
||||
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()) {
|
||||
receiveOutOfOrderAction(jinglePacket, JinglePacket.Action.SESSION_ACCEPT);
|
||||
receiveOutOfOrderAction(jinglePacket, Jingle.Action.SESSION_ACCEPT);
|
||||
return;
|
||||
}
|
||||
final ListenableFuture<RtpContentMap> future =
|
||||
receiveRtpContentMap(jinglePacket, this.omemoVerification.hasFingerprint());
|
||||
receiveRtpContentMap(jingle, this.omemoVerification.hasFingerprint());
|
||||
Futures.addCallback(
|
||||
future,
|
||||
new FutureCallback<>() {
|
||||
|
@ -1264,7 +1267,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
|||
}
|
||||
|
||||
private void receiveSessionAccept(
|
||||
final JinglePacket jinglePacket, final RtpContentMap contentMap) {
|
||||
final Iq jinglePacket, final RtpContentMap contentMap) {
|
||||
try {
|
||||
contentMap.requireContentDescriptions();
|
||||
contentMap.requireDTLSFingerprint();
|
||||
|
@ -1409,7 +1412,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
|||
}
|
||||
|
||||
private void failureToPerformAction(
|
||||
final JinglePacket.Action action, final Throwable throwable) {
|
||||
final Jingle.Action action, final Throwable throwable) {
|
||||
if (isTerminated()) {
|
||||
return;
|
||||
}
|
||||
|
@ -1480,8 +1483,8 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
|||
return;
|
||||
}
|
||||
transitionOrThrow(State.SESSION_ACCEPTED);
|
||||
final JinglePacket sessionAccept =
|
||||
rtpContentMap.toJinglePacket(JinglePacket.Action.SESSION_ACCEPT, id.sessionId);
|
||||
final Iq sessionAccept =
|
||||
rtpContentMap.toJinglePacket(Jingle.Action.SESSION_ACCEPT, id.sessionId);
|
||||
send(sessionAccept);
|
||||
}
|
||||
|
||||
|
@ -1951,8 +1954,8 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
|||
return;
|
||||
}
|
||||
this.transitionOrThrow(targetState);
|
||||
final JinglePacket sessionInitiate =
|
||||
rtpContentMap.toJinglePacket(JinglePacket.Action.SESSION_INITIATE, id.sessionId);
|
||||
final Iq sessionInitiate =
|
||||
rtpContentMap.toJinglePacket(Jingle.Action.SESSION_INITIATE, id.sessionId);
|
||||
send(sessionInitiate);
|
||||
}
|
||||
|
||||
|
@ -2020,9 +2023,9 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
|||
+ contentName);
|
||||
return;
|
||||
}
|
||||
final JinglePacket jinglePacket =
|
||||
transportInfo.toJinglePacket(JinglePacket.Action.TRANSPORT_INFO, id.sessionId);
|
||||
send(jinglePacket);
|
||||
final Iq iq =
|
||||
transportInfo.toJinglePacket(Jingle.Action.TRANSPORT_INFO, id.sessionId);
|
||||
send(iq);
|
||||
}
|
||||
|
||||
public RtpEndUserState getEndUserState() {
|
||||
|
@ -2340,7 +2343,8 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
|||
this.jingleConnectionManager.ensureConnectionIsRegistered(this);
|
||||
this.webRTCWrapper.setup(this.xmppConnectionService);
|
||||
this.webRTCWrapper.initializePeerConnection(media, iceServers, trickle);
|
||||
this.webRTCWrapper.setMicrophoneEnabledOrThrow(callIntegration.isMicrophoneEnabled());
|
||||
// this.webRTCWrapper.setMicrophoneEnabledOrThrow(callIntegration.isMicrophoneEnabled());
|
||||
this.webRTCWrapper.setMicrophoneEnabledOrThrow(true);
|
||||
}
|
||||
|
||||
private void acceptCallFromProposed() {
|
||||
|
@ -2375,8 +2379,8 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
|||
}
|
||||
|
||||
private void sendJingleMessage(final String action, final Jid to) {
|
||||
final MessagePacket messagePacket = new MessagePacket();
|
||||
messagePacket.setType(MessagePacket.TYPE_CHAT); // we want to carbon copy those
|
||||
final var messagePacket = new im.conversations.android.xmpp.model.stanza.Message();
|
||||
messagePacket.setType(im.conversations.android.xmpp.model.stanza.Message.Type.CHAT); // we want to carbon copy those
|
||||
messagePacket.setTo(to);
|
||||
final Element intent =
|
||||
messagePacket
|
||||
|
@ -2397,7 +2401,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
|||
|
||||
private void sendJingleMessageFinish(final Reason reason) {
|
||||
final var account = id.getAccount();
|
||||
final MessagePacket messagePacket =
|
||||
final var messagePacket =
|
||||
xmppConnectionService
|
||||
.getMessageGenerator()
|
||||
.sessionFinish(id.with, id.sessionId, reason);
|
||||
|
@ -2556,34 +2560,34 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
|||
|
||||
private void initiateIceRestart(final RtpContentMap rtpContentMap) {
|
||||
final RtpContentMap transportInfo = rtpContentMap.transportInfo();
|
||||
final JinglePacket jinglePacket =
|
||||
transportInfo.toJinglePacket(JinglePacket.Action.TRANSPORT_INFO, id.sessionId);
|
||||
Log.d(Config.LOGTAG, "initiating ice restart: " + jinglePacket);
|
||||
jinglePacket.setTo(id.with);
|
||||
final Iq iq =
|
||||
transportInfo.toJinglePacket(Jingle.Action.TRANSPORT_INFO, id.sessionId);
|
||||
Log.d(Config.LOGTAG, "initiating ice restart: " + iq);
|
||||
iq.setTo(id.with);
|
||||
xmppConnectionService.sendIqPacket(
|
||||
id.account,
|
||||
jinglePacket,
|
||||
(account, response) -> {
|
||||
if (response.getType() == IqPacket.TYPE.RESULT) {
|
||||
iq,
|
||||
(response) -> {
|
||||
if (response.getType() == Iq.Type.RESULT) {
|
||||
Log.d(Config.LOGTAG, "received success to our ice restart");
|
||||
setLocalContentMap(rtpContentMap);
|
||||
webRTCWrapper.setIsReadyToReceiveIceCandidates(true);
|
||||
return;
|
||||
}
|
||||
if (response.getType() == IqPacket.TYPE.ERROR) {
|
||||
if (response.getType() == Iq.Type.ERROR) {
|
||||
if (isTieBreak(response)) {
|
||||
Log.d(Config.LOGTAG, "received tie-break as result of ice restart");
|
||||
return;
|
||||
}
|
||||
handleIqErrorResponse(response);
|
||||
}
|
||||
if (response.getType() == IqPacket.TYPE.TIMEOUT) {
|
||||
if (response.getType() == Iq.Type.TIMEOUT) {
|
||||
handleIqTimeoutResponse(response);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private boolean isTieBreak(final IqPacket response) {
|
||||
private boolean isTieBreak(final Iq response) {
|
||||
final Element error = response.findChild("error");
|
||||
return error != null && error.hasChild("tie-break", Namespace.JINGLE_ERRORS);
|
||||
}
|
||||
|
@ -2604,7 +2608,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
|||
|
||||
@Override
|
||||
public void onFailure(@NonNull Throwable throwable) {
|
||||
failureToPerformAction(JinglePacket.Action.CONTENT_ADD, throwable);
|
||||
failureToPerformAction(Jingle.Action.CONTENT_ADD, throwable);
|
||||
}
|
||||
},
|
||||
MoreExecutors.directExecutor());
|
||||
|
@ -2612,21 +2616,21 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
|||
|
||||
private void sendContentAdd(final RtpContentMap contentAdd) {
|
||||
|
||||
final JinglePacket jinglePacket =
|
||||
contentAdd.toJinglePacket(JinglePacket.Action.CONTENT_ADD, id.sessionId);
|
||||
jinglePacket.setTo(id.with);
|
||||
final Iq iq =
|
||||
contentAdd.toJinglePacket(Jingle.Action.CONTENT_ADD, id.sessionId);
|
||||
iq.setTo(id.with);
|
||||
xmppConnectionService.sendIqPacket(
|
||||
id.account,
|
||||
jinglePacket,
|
||||
(connection, response) -> {
|
||||
if (response.getType() == IqPacket.TYPE.RESULT) {
|
||||
iq,
|
||||
(response) -> {
|
||||
if (response.getType() == Iq.Type.RESULT) {
|
||||
Log.d(
|
||||
Config.LOGTAG,
|
||||
id.getAccount().getJid().asBareJid()
|
||||
+ ": received ACK to our content-add");
|
||||
return;
|
||||
}
|
||||
if (response.getType() == IqPacket.TYPE.ERROR) {
|
||||
if (response.getType() == Iq.Type.ERROR) {
|
||||
if (isTieBreak(response)) {
|
||||
this.outgoingContentAdd = null;
|
||||
Log.d(Config.LOGTAG, "received tie-break as result of our content-add");
|
||||
|
@ -2634,7 +2638,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
|||
}
|
||||
handleIqErrorResponse(response);
|
||||
}
|
||||
if (response.getType() == IqPacket.TYPE.TIMEOUT) {
|
||||
if (response.getType() == Iq.Type.TIMEOUT) {
|
||||
handleIqTimeoutResponse(response);
|
||||
}
|
||||
});
|
||||
|
@ -2782,7 +2786,12 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
|||
|
||||
@Override
|
||||
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
|
||||
|
@ -2827,13 +2836,13 @@ public class JingleRtpConnection extends AbstractJingleConnection
|
|||
|
||||
private void discoverIceServers(final OnIceServersDiscovered onIceServersDiscovered) {
|
||||
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.addChild("services", Namespace.EXTERNAL_SERVICE_DISCOVERY);
|
||||
xmppConnectionService.sendIqPacket(
|
||||
id.account,
|
||||
request,
|
||||
(account, response) -> {
|
||||
(response) -> {
|
||||
final var iceServers = IceServers.parse(response);
|
||||
if (iceServers.isEmpty()) {
|
||||
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.Group;
|
||||
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.RtpDescription;
|
||||
import im.conversations.android.xmpp.model.jingle.Jingle;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
|
@ -39,7 +39,7 @@ public class RtpContentMap extends AbstractContentMap<RtpDescription, IceUdpTran
|
|||
super(group, contents);
|
||||
}
|
||||
|
||||
public static RtpContentMap of(final JinglePacket jinglePacket) {
|
||||
public static RtpContentMap of(final Jingle jinglePacket) {
|
||||
final Map<String, DescriptionTransport<RtpDescription, IceUdpTransportInfo>> contents =
|
||||
of(jinglePacket.getJingleContents());
|
||||
if (isOmemoVerified(contents)) {
|
||||
|
@ -53,7 +53,7 @@ public class RtpContentMap extends AbstractContentMap<RtpDescription, IceUdpTran
|
|||
Map<String, DescriptionTransport<RtpDescription, IceUdpTransportInfo>> contents) {
|
||||
final Collection<DescriptionTransport<RtpDescription, IceUdpTransportInfo>> values =
|
||||
contents.values();
|
||||
if (values.size() == 0) {
|
||||
if (values.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
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