This commit is contained in:
Arne 2024-09-10 08:14:55 +02:00
parent 76f6026df9
commit 4907e6f015
320 changed files with 11298 additions and 2963 deletions

View file

@ -44,14 +44,16 @@ configurations {
dependencies { dependencies {
androidTestImplementation 'tools.fastlane:screengrab:2.1.1' androidTestImplementation 'tools.fastlane:screengrab:2.1.1'
androidTestImplementation 'junit:junit:4.13.2' androidTestImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test:runner:1.3.0' androidTestImplementation 'androidx.test:runner:1.6.2'
androidTestImplementation 'androidx.test:rules:1.3.0' androidTestImplementation 'androidx.test:rules:1.6.1'
androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.ext:junit:1.2.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
implementation "androidx.core:core:1.10.1" implementation "androidx.core:core:1.13.1"
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.2'
implementation project(':libs:annotation')
annotationProcessor project(':libs:annotation-processor')
implementation 'androidx.viewpager:viewpager:1.0.0' implementation 'androidx.viewpager:viewpager:1.0.0'
@ -65,17 +67,17 @@ dependencies {
conversationsPlaystoreImplementation("com.android.installreferrer:installreferrer:2.2") conversationsPlaystoreImplementation("com.android.installreferrer:installreferrer:2.2")
quicksyPlaystoreImplementation 'com.google.android.gms:play-services-auth-api-phone:18.1.0' quicksyPlaystoreImplementation 'com.google.android.gms:play-services-auth-api-phone:18.1.0'
implementation 'com.github.open-keychain.open-keychain:openpgp-api:v5.7.1' implementation 'com.github.open-keychain.open-keychain:openpgp-api:v5.7.1'
implementation("com.github.CanHub:Android-Image-Cropper:2.0.0") implementation("com.github.CanHub:Android-Image-Cropper:2.2.0")
implementation 'androidx.appcompat:appcompat:1.7.0' implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'androidx.exifinterface:exifinterface:1.3.7' implementation 'androidx.exifinterface:exifinterface:1.3.7'
implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.cardview:cardview:1.0.0'
implementation "androidx.preference:preference:1.2.1" implementation "androidx.preference:preference:1.2.1"
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'com.google.android.material:material:1.12.0' implementation 'com.google.android.material:material:1.12.0'
implementation 'androidx.work:work-runtime:2.9.0' implementation 'androidx.work:work-runtime:2.9.1'
implementation "androidx.emoji2:emoji2:1.4.0" implementation "androidx.emoji2:emoji2:1.5.0"
freeImplementation "androidx.emoji2:emoji2-bundled:1.4.0" freeImplementation "androidx.emoji2:emoji2-bundled:1.5.0"
implementation 'org.bouncycastle:bcmail-jdk18on:1.78.1' implementation 'org.bouncycastle:bcmail-jdk18on:1.78.1'
implementation 'com.google.zxing:core:3.5.3' implementation 'com.google.zxing:core:3.5.3'
@ -92,7 +94,7 @@ dependencies {
implementation 'org.jxmpp:jxmpp-jid:1.0.3' implementation 'org.jxmpp:jxmpp-jid:1.0.3'
implementation 'org.jxmpp:jxmpp-stringprep-libidn:1.0.3' implementation 'org.jxmpp:jxmpp-stringprep-libidn:1.0.3'
implementation 'org.osmdroid:osmdroid-android:6.1.11' implementation 'org.osmdroid:osmdroid-android:6.1.16'
implementation 'org.hsluv:hsluv:0.2' implementation 'org.hsluv:hsluv:0.2'
implementation 'org.conscrypt:conscrypt-android:2.5.2' implementation 'org.conscrypt:conscrypt-android:2.5.2'
implementation 'me.drakeet.support:toastcompat:1.1.0' implementation 'me.drakeet.support:toastcompat:1.1.0'
@ -102,11 +104,11 @@ dependencies {
implementation "com.squareup.retrofit2:converter-gson:2.11.0" implementation "com.squareup.retrofit2:converter-gson:2.11.0"
implementation "com.squareup.okhttp3:okhttp:4.12.0" implementation "com.squareup.okhttp3:okhttp:4.12.0"
implementation 'com.google.guava:guava:32.1.3-android' implementation 'com.google.guava:guava:33.0.0-android'
implementation 'io.michaelrocks:libphonenumber-android:8.13.35' implementation 'io.michaelrocks:libphonenumber-android:8.13.35'
implementation 'im.conversations.webrtc:webrtc-android:119.0.1' implementation 'im.conversations.webrtc:webrtc-android:119.0.1'
implementation 'io.github.nishkarsh:android-permissions:2.1.6' implementation 'io.github.nishkarsh:android-permissions:2.1.6'
implementation 'androidx.recyclerview:recyclerview:1.1.0' implementation 'androidx.recyclerview:recyclerview:1.3.2'
implementation 'androidx.documentfile:documentfile:1.0.1' implementation 'androidx.documentfile:documentfile:1.0.1'
implementation 'androidx.browser:browser:1.8.0' implementation 'androidx.browser:browser:1.8.0'
implementation 'com.github.martin-stone:hsv-alpha-color-picker-android:3.1.0' implementation 'com.github.martin-stone:hsv-alpha-color-picker-android:3.1.0'

View 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'
}

View file

@ -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);
}
}
}

View file

@ -0,0 +1,6 @@
apply plugin: "java-library"
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}

View file

@ -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 "";
}

View file

@ -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
View file

@ -1,6 +1,7 @@
-dontobfuscate -dontobfuscate
-keep class eu.siacs.conversations.** -keep class eu.siacs.conversations.**
-keep class im.conversations.**
-keep class org.whispersystems.** -keep class org.whispersystems.**

View file

@ -1 +1,3 @@
include ':libs:annotation', ':libs:annotation-processor:'
rootProject.name = 'Conversations' rootProject.name = 'Conversations'

View 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>

View file

@ -142,10 +142,6 @@
</intent-filter> </intent-filter>
</service> </service>
<receiver
android:name=".receiver.WorkManagerEventReceiver"
android:exported="false" />
<receiver <receiver
android:name=".receiver.SystemEventReceiver" android:name=".receiver.SystemEventReceiver"
android:exported="false"> android:exported="false">

View file

@ -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;
}
}

View 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);
}
}

View file

@ -10,6 +10,8 @@ import androidx.preference.PreferenceManager;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import java.security.SecureRandom;
public class AppSettings { public class AppSettings {
public static final String KEEP_FOREGROUND_SERVICE = "enable_foreground_service"; public static final String KEEP_FOREGROUND_SERVICE = "enable_foreground_service";
@ -46,6 +48,9 @@ public class AppSettings {
public static final String LARGE_FONT = "large_font"; public static final String LARGE_FONT = "large_font";
public static final String SHOW_LINK_PREVIEWS = "show_link_previews"; public static final String SHOW_LINK_PREVIEWS = "show_link_previews";
private static final String ACCEPT_INVITES_FROM_STRANGERS = "accept_invites_from_strangers";
private static final String INSTALLATION_ID = "im.conversations.android.install_id";
private final Context context; private final Context context;
public AppSettings(final Context context) { public AppSettings(final Context context) {
@ -143,4 +148,25 @@ public class AppSettings {
public boolean isRequireChannelBinding() { public boolean isRequireChannelBinding() {
return getBooleanPreference(REQUIRE_CHANNEL_BINDING, R.bool.require_channel_binding); return getBooleanPreference(REQUIRE_CHANNEL_BINDING, R.bool.require_channel_binding);
} }
public synchronized long getInstallationId() {
final var sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
final long existing = sharedPreferences.getLong(INSTALLATION_ID, 0);
if (existing != 0) {
return existing;
}
final var secureRandom = new SecureRandom();
final var installationId = secureRandom.nextLong();
sharedPreferences.edit().putLong(INSTALLATION_ID, installationId).apply();
return installationId;
}
public synchronized void resetInstallationId() {
final var secureRandom = new SecureRandom();
final var installationId = secureRandom.nextLong();
PreferenceManager.getDefaultSharedPreferences(context)
.edit()
.putLong(INSTALLATION_ID, installationId)
.apply();
}
} }

View file

@ -1,5 +1,6 @@
package eu.siacs.conversations; package eu.siacs.conversations;
import android.annotation.SuppressLint;
import android.app.Application; import android.app.Application;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
@ -15,9 +16,17 @@ import eu.siacs.conversations.utils.ThemeHelper;
public class Conversations extends Application { public class Conversations extends Application {
@SuppressLint("StaticFieldLeak")
private static Context CONTEXT;
public static Context getContext() {
return Conversations.CONTEXT;
}
@Override @Override
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
CONTEXT = this.getApplicationContext();
ExceptionHelper.init(getApplicationContext()); ExceptionHelper.init(getApplicationContext());
applyThemeSettings(); applyThemeSettings();
} }

View file

@ -61,7 +61,6 @@ import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xml.Namespace;
import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.Jid;
import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded; import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded;
import eu.siacs.conversations.xmpp.OnIqPacketReceived;
import eu.siacs.conversations.xmpp.jingle.DescriptionTransport; import eu.siacs.conversations.xmpp.jingle.DescriptionTransport;
import eu.siacs.conversations.xmpp.jingle.OmemoVerification; import eu.siacs.conversations.xmpp.jingle.OmemoVerification;
import eu.siacs.conversations.xmpp.jingle.OmemoVerifiedRtpContentMap; import eu.siacs.conversations.xmpp.jingle.OmemoVerifiedRtpContentMap;
@ -70,8 +69,7 @@ import eu.siacs.conversations.xmpp.jingle.stanzas.IceUdpTransportInfo;
import eu.siacs.conversations.xmpp.jingle.stanzas.OmemoVerifiedIceUdpTransportInfo; import eu.siacs.conversations.xmpp.jingle.stanzas.OmemoVerifiedIceUdpTransportInfo;
import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription; import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription;
import eu.siacs.conversations.xmpp.pep.PublishOptions; import eu.siacs.conversations.xmpp.pep.PublishOptions;
import eu.siacs.conversations.xmpp.stanzas.IqPacket; import im.conversations.android.xmpp.model.stanza.Iq;
import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
@ -392,20 +390,18 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
Log.d(Config.LOGTAG, getLogprefix(account) + "publishOwnDeviceIdIfNeeded called, but PEP is broken. Ignoring... "); Log.d(Config.LOGTAG, getLogprefix(account) + "publishOwnDeviceIdIfNeeded called, but PEP is broken. Ignoring... ");
return; return;
} }
IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveDeviceIds(account.getJid().asBareJid()); Iq packet = mXmppConnectionService.getIqGenerator().retrieveDeviceIds(account.getJid().asBareJid());
mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { mXmppConnectionService.sendIqPacket(account, packet, response -> {
@Override if (response.getType() == Iq.Type.TIMEOUT) {
public void onIqPacketReceived(Account account, IqPacket packet) {
if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
Log.d(Config.LOGTAG, getLogprefix(account) + "Timeout received while retrieving own Device Ids."); Log.d(Config.LOGTAG, getLogprefix(account) + "Timeout received while retrieving own Device Ids.");
} else { } else {
//TODO consider calling registerDevices only after item-not-found to account for broken PEPs //TODO consider calling registerDevices only after item-not-found to account for broken PEPs
Element item = mXmppConnectionService.getIqParser().getItem(packet); final Element item = IqParser.getItem(response);
Set<Integer> deviceIds = mXmppConnectionService.getIqParser().deviceIds(item); final Set<Integer> deviceIds = IqParser.deviceIds(item);
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": retrieved own device list: " + deviceIds); Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": retrieved own device list: " + deviceIds);
registerDevices(account.getJid().asBareJid(), 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) { private void publishDeviceIdsAndRefineAccessModel(final Set<Integer> ids, final boolean firstAttempt) {
final Bundle publishOptions = account.getXmppConnection().getFeatures().pepPublishOptions() ? PublishOptions.openAccess() : null; final Bundle publishOptions = account.getXmppConnection().getFeatures().pepPublishOptions() ? PublishOptions.openAccess() : null;
IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(ids, publishOptions); final var publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(ids, publishOptions);
mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() { mXmppConnectionService.sendIqPacket(account, publish, response -> {
@Override final Element error = response.getType() == Iq.Type.ERROR ? response.findChild("error") : null;
public void onIqPacketReceived(Account account, IqPacket packet) { final boolean preConditionNotMet = PublishOptions.preconditionNotMet(response);
final Element error = packet.getType() == IqPacket.TYPE.ERROR ? packet.findChild("error") : null;
final boolean preConditionNotMet = PublishOptions.preconditionNotMet(packet);
if (firstAttempt && preConditionNotMet) { if (firstAttempt && preConditionNotMet) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": precondition wasn't met for device list. pushing node configuration"); 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() { 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); account.setOption(Account.OPTION_REQUIRES_ACCESS_MODE_CHANGE, false);
mXmppConnectionService.databaseBackend.updateAccount(account); mXmppConnectionService.databaseBackend.updateAccount(account);
} }
if (packet.getType() == IqPacket.TYPE.ERROR) { if (response.getType() == Iq.Type.ERROR) {
if (preConditionNotMet) { if (preConditionNotMet) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": device list pre condition still not met on second attempt"); Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": device list pre condition still not met on second attempt");
} else if (error != null) { } else if (error != null) {
pepBroken = true; 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.initSign(x509PrivateKey, SECURE_RANDOM);
verifier.update(axolotlPublicKey.serialize()); verifier.update(axolotlPublicKey.serialize());
byte[] signature = verifier.sign(); byte[] signature = verifier.sign();
IqPacket packet = mXmppConnectionService.getIqGenerator().publishVerification(signature, chain, getOwnDeviceId()); final Iq packet = mXmppConnectionService.getIqGenerator().publishVerification(signature, chain, getOwnDeviceId());
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ": publish verification for device " + getOwnDeviceId()); Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ": publish verification for device " + getOwnDeviceId());
mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { mXmppConnectionService.sendIqPacket(account, packet, response -> {
@Override
public void onIqPacketReceived(final Account account, IqPacket packet) {
String node = AxolotlService.PEP_VERIFICATION + ":" + getOwnDeviceId(); String node = AxolotlService.PEP_VERIFICATION + ":" + getOwnDeviceId();
mXmppConnectionService.pushNodeConfiguration(account, node, PublishOptions.openAccess(), new XmppConnectionService.OnConfigurationPushed() { mXmppConnectionService.pushNodeConfiguration(account, node, PublishOptions.openAccess(), new XmppConnectionService.OnConfigurationPushed() {
@Override @Override
@ -525,7 +516,6 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe); publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe);
} }
}); });
}
}); });
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
@ -549,34 +539,32 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
if (this.changeAccessMode.get()) { if (this.changeAccessMode.get()) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": server gained publish-options capabilities. changing access model"); Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": server gained publish-options capabilities. changing access model");
} }
IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(account.getJid().asBareJid(), getOwnDeviceId()); final Iq packet = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(account.getJid().asBareJid(), getOwnDeviceId());
mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { mXmppConnectionService.sendIqPacket(account, packet, response -> {
@Override
public void onIqPacketReceived(Account account, IqPacket packet) {
if (packet.getType() == IqPacket.TYPE.TIMEOUT) { if (response.getType() == Iq.Type.TIMEOUT) {
return; //ignore timeout. do nothing return; //ignore timeout. do nothing
} }
if (packet.getType() == IqPacket.TYPE.ERROR) { if (response.getType() == Iq.Type.ERROR) {
Element error = packet.findChild("error"); Element error = response.findChild("error");
if (error == null || !error.hasChild("item-not-found")) { if (error == null || !error.hasChild("item-not-found")) {
pepBroken = true; 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; return;
} }
} }
PreKeyBundle bundle = mXmppConnectionService.getIqParser().bundle(packet); PreKeyBundle bundle = IqParser.bundle(response);
Map<Integer, ECPublicKey> keys = mXmppConnectionService.getIqParser().preKeyPublics(packet); final Map<Integer, ECPublicKey> keys = IqParser.preKeyPublics(response);
boolean flush = false; boolean flush = false;
if (bundle == null) { 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); bundle = new PreKeyBundle(-1, -1, -1, null, -1, null, null, null);
flush = true; flush = true;
} }
if (keys == null) { 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 { try {
boolean changed = false; boolean changed = false;
@ -652,7 +640,6 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
} catch (InvalidKeyException e) { } catch (InvalidKeyException e) {
Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Failed to publish bundle " + getOwnDeviceId() + ", reason: " + e.getMessage()); 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 wipe,
final boolean firstAttempt) { final boolean firstAttempt) {
final Bundle publishOptions = account.getXmppConnection().getFeatures().pepPublishOptions() ? PublishOptions.openAccess() : null; final Bundle publishOptions = account.getXmppConnection().getFeatures().pepPublishOptions() ? PublishOptions.openAccess() : null;
final IqPacket publish = mXmppConnectionService.getIqGenerator().publishBundles( final Iq publish = mXmppConnectionService.getIqGenerator().publishBundles(
signedPreKeyRecord, axolotlStore.getIdentityKeyPair().getPublicKey(), signedPreKeyRecord, axolotlStore.getIdentityKeyPair().getPublicKey(),
preKeyRecords, getOwnDeviceId(), publishOptions); preKeyRecords, getOwnDeviceId(), publishOptions);
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ": Bundle " + getOwnDeviceId() + " in PEP not current. Publishing..."); Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ": Bundle " + getOwnDeviceId() + " in PEP not current. Publishing...");
mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() { mXmppConnectionService.sendIqPacket(account, publish, response -> {
@Override final boolean preconditionNotMet = PublishOptions.preconditionNotMet(response);
public void onIqPacketReceived(final Account account, IqPacket packet) {
final boolean preconditionNotMet = PublishOptions.preconditionNotMet(packet);
if (firstAttempt && preconditionNotMet) { if (firstAttempt && preconditionNotMet) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": precondition wasn't met for bundle. pushing node configuration"); Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": precondition wasn't met for bundle. pushing node configuration");
final String node = AxolotlService.PEP_BUNDLES + ":" + getOwnDeviceId(); final String node = AxolotlService.PEP_BUNDLES + ":" + getOwnDeviceId();
@ -691,7 +676,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
publishDeviceBundle(signedPreKeyRecord, preKeyRecords, announceAfter, wipe, false); 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. "); Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Successfully published bundle. ");
if (wipe) { if (wipe) {
wipeOtherPepDevices(); wipeOtherPepDevices();
@ -699,15 +684,14 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId()); Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId());
publishOwnDeviceIdIfNeeded(); publishOwnDeviceIdIfNeeded();
} }
} else if (packet.getType() == IqPacket.TYPE.ERROR) { } else if (response.getType() == Iq.Type.ERROR) {
if (preconditionNotMet) { if (preconditionNotMet) {
Log.d(Config.LOGTAG, getLogprefix(account) + "bundle precondition still not met after second attempt"); Log.d(Config.LOGTAG, getLogprefix(account) + "bundle precondition still not met after second attempt");
} else { } 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; pepBroken = true;
} }
}
}); });
} }
@ -759,9 +743,9 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
return Futures.immediateFuture(session); return Futures.immediateFuture(session);
} }
final SettableFuture<XmppAxolotlSession> future = SettableFuture.create(); final SettableFuture<XmppAxolotlSession> future = SettableFuture.create();
final IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveVerificationForDevice(jid, address.getDeviceId()); final Iq packet = mXmppConnectionService.getIqGenerator().retrieveVerificationForDevice(jid, address.getDeviceId());
mXmppConnectionService.sendIqPacket(account, packet, (account, response) -> { mXmppConnectionService.sendIqPacket(account, packet, response -> {
Pair<X509Certificate[], byte[]> verification = mXmppConnectionService.getIqParser().verification(response); Pair<X509Certificate[], byte[]> verification = IqParser.verification(response);
if (verification != null) { if (verification != null) {
try { try {
Signature verifier = Signature.getInstance("sha256WithRSA"); Signature verifier = Signature.getInstance("sha256WithRSA");
@ -846,7 +830,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
} }
private void fetchDeviceIds(final Jid jid, OnDeviceIdsFetched callback) { private void fetchDeviceIds(final Jid jid, OnDeviceIdsFetched callback) {
IqPacket packet; final Iq packet;
synchronized (this.fetchDeviceIdsMap) { synchronized (this.fetchDeviceIdsMap) {
List<OnDeviceIdsFetched> callbacks = this.fetchDeviceIdsMap.get(jid); List<OnDeviceIdsFetched> callbacks = this.fetchDeviceIdsMap.get(jid);
if (callbacks != null) { if (callbacks != null) {
@ -866,11 +850,11 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
} }
} }
if (packet != null) { if (packet != null) {
mXmppConnectionService.sendIqPacket(account, packet, (account, response) -> { mXmppConnectionService.sendIqPacket(account, packet, response -> {
if (response.getType() == IqPacket.TYPE.RESULT) { if (response.getType() == Iq.Type.RESULT) {
fetchDeviceListStatus.put(jid, true); fetchDeviceListStatus.put(jid, true);
Element item = mXmppConnectionService.getIqParser().getItem(response); final Element item = IqParser.getItem(response);
Set<Integer> deviceIds = mXmppConnectionService.getIqParser().deviceIds(item); final Set<Integer> deviceIds = IqParser.deviceIds(item);
registerDevices(jid, deviceIds); registerDevices(jid, deviceIds);
final List<OnDeviceIdsFetched> callbacks; final List<OnDeviceIdsFetched> callbacks;
synchronized (fetchDeviceIdsMap) { synchronized (fetchDeviceIdsMap) {
@ -882,7 +866,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
} }
} }
} else { } else {
if (response.getType() == IqPacket.TYPE.TIMEOUT) { if (response.getType() == Iq.Type.TIMEOUT) {
fetchDeviceListStatus.remove(jid); fetchDeviceListStatus.remove(jid);
} else { } else {
fetchDeviceListStatus.put(jid, false); fetchDeviceListStatus.put(jid, false);
@ -929,16 +913,15 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
} }
final Jid jid = Jid.of(address.getName()); final Jid jid = Jid.of(address.getName());
final boolean oneOfOurs = jid.asBareJid().equals(account.getJid().asBareJid()); final boolean oneOfOurs = jid.asBareJid().equals(account.getJid().asBareJid());
IqPacket bundlesPacket = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(jid, address.getDeviceId()); final Iq bundlesPacket = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(jid, address.getDeviceId());
mXmppConnectionService.sendIqPacket(account, bundlesPacket, (account, packet) -> { mXmppConnectionService.sendIqPacket(account, bundlesPacket, packet -> {
if (packet.getType() == IqPacket.TYPE.TIMEOUT) { if (packet.getType() == Iq.Type.TIMEOUT) {
fetchStatusMap.put(address, FetchStatus.TIMEOUT); fetchStatusMap.put(address, FetchStatus.TIMEOUT);
sessionSettableFuture.setException(new CryptoFailedException("Unable to build session. Timeout")); sessionSettableFuture.setException(new CryptoFailedException("Unable to build session. Timeout"));
} else if (packet.getType() == IqPacket.TYPE.RESULT) { } else if (packet.getType() == Iq.Type.RESULT) {
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received preKey IQ packet, processing..."); Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received preKey IQ packet, processing...");
final IqParser parser = mXmppConnectionService.getIqParser(); final List<PreKeyBundle> preKeyBundleList = IqParser.preKeys(packet);
final List<PreKeyBundle> preKeyBundleList = parser.preKeys(packet); final PreKeyBundle bundle = IqParser.bundle(packet);
final PreKeyBundle bundle = parser.bundle(packet);
if (preKeyBundleList.isEmpty() || bundle == null) { if (preKeyBundleList.isEmpty() || bundle == null) {
Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "preKey IQ packet invalid: " + packet); Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "preKey IQ packet invalid: " + packet);
fetchStatusMap.put(address, FetchStatus.ERROR); fetchStatusMap.put(address, FetchStatus.ERROR);
@ -1544,7 +1527,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
axolotlMessage.addDevice(session, true); axolotlMessage.addDevice(session, true);
try { try {
final Jid jid = Jid.of(session.getRemoteAddress().getName()); final Jid jid = Jid.of(session.getRemoteAddress().getName());
MessagePacket packet = mXmppConnectionService.getMessageGenerator().generateKeyTransportMessage(jid, axolotlMessage); final var packet = mXmppConnectionService.getMessageGenerator().generateKeyTransportMessage(jid, axolotlMessage);
mXmppConnectionService.sendMessagePacket(account, packet); mXmppConnectionService.sendMessagePacket(account, packet);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
throw new Error("Remote addresses are created from jid and should convert back to jid", e); throw new Error("Remote addresses are created from jid and should convert back to jid", e);

View file

@ -146,10 +146,10 @@ import eu.siacs.conversations.xmpp.chatstate.ChatState;
import eu.siacs.conversations.xmpp.forms.Data; import eu.siacs.conversations.xmpp.forms.Data;
import eu.siacs.conversations.xmpp.forms.Option; import eu.siacs.conversations.xmpp.forms.Option;
import eu.siacs.conversations.xmpp.mam.MamReference; import eu.siacs.conversations.xmpp.mam.MamReference;
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
import static eu.siacs.conversations.entities.Bookmark.printableValue; import static eu.siacs.conversations.entities.Bookmark.printableValue;
import im.conversations.android.xmpp.model.stanza.Iq;
public class Conversation extends AbstractEntity implements Blockable, Comparable<Conversation>, Conversational, AvatarService.Avatarable { public class Conversation extends AbstractEntity implements Blockable, Comparable<Conversation>, Conversational, AvatarService.Avatarable {
public static final String TABLENAME = "conversations"; public static final String TABLENAME = "conversations";
@ -1600,7 +1600,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
show(); show();
CommandSession session = new CommandSession(command.getAttribute("name"), command.getAttribute("node"), xmppConnectionService); CommandSession session = new CommandSession(command.getAttribute("name"), command.getAttribute("node"), xmppConnectionService);
final IqPacket packet = new IqPacket(IqPacket.TYPE.SET); final var packet = new Iq(Iq.Type.SET);
packet.setTo(command.getAttributeAsJid("jid")); packet.setTo(command.getAttributeAsJid("jid"));
final Element c = packet.addChild("command", Namespace.COMMANDS); final Element c = packet.addChild("command", Namespace.COMMANDS);
c.setAttribute("node", command.getAttribute("node")); c.setAttribute("node", command.getAttribute("node"));
@ -1618,7 +1618,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
} }
}, 1000); }, 1000);
} else { } else {
xmppConnectionService.sendIqPacket(getAccount(), packet, (a, iq) -> { xmppConnectionService.sendIqPacket(getAccount(), packet, (iq) -> {
session.updateWithResponse(iq); session.updateWithResponse(iq);
}, 120L); }, 120L);
} }
@ -1645,7 +1645,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
public void startMucConfig(XmppConnectionService xmppConnectionService) { public void startMucConfig(XmppConnectionService xmppConnectionService) {
MucConfigSession session = new MucConfigSession(xmppConnectionService); MucConfigSession session = new MucConfigSession(xmppConnectionService);
final IqPacket packet = new IqPacket(IqPacket.TYPE.GET); final var packet = new Iq(Iq.Type.GET);
packet.setTo(Conversation.this.getJid().asBareJid()); packet.setTo(Conversation.this.getJid().asBareJid());
packet.addChild("query", "http://jabber.org/protocol/muc#owner"); packet.addChild("query", "http://jabber.org/protocol/muc#owner");
@ -1661,7 +1661,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
} }
}, 1000); }, 1000);
} else { } else {
xmppConnectionService.sendIqPacket(getAccount(), packet, (a, iq) -> { xmppConnectionService.sendIqPacket(getAccount(), packet, (iq) -> {
session.updateWithResponse(iq); session.updateWithResponse(iq);
}, 120L); }, 120L);
} }
@ -2782,7 +2782,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
protected Item mkItem(Element el, int pos) { protected Item mkItem(Element el, int pos) {
int viewType = TYPE_ERROR; int viewType = TYPE_ERROR;
if (response != null && response.getType() == IqPacket.TYPE.RESULT) { if (response != null && response.getType() == Iq.Type.RESULT) {
if (el.getName().equals("note")) { if (el.getName().equals("note")) {
viewType = TYPE_NOTE; viewType = TYPE_NOTE;
} else if (el.getNamespace().equals("jabber:x:oob")) { } else if (el.getNamespace().equals("jabber:x:oob")) {
@ -2883,7 +2883,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
protected String mTitle; protected String mTitle;
protected String mNode; protected String mNode;
protected CommandPageBinding mBinding = null; protected CommandPageBinding mBinding = null;
protected IqPacket response = null; protected Iq response = null;
protected Element responseElement = null; protected Element responseElement = null;
protected boolean expectingRemoval = false; protected boolean expectingRemoval = false;
protected List<Field> reported = null; protected List<Field> reported = null;
@ -2893,7 +2893,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
protected GridLayoutManager layoutManager; protected GridLayoutManager layoutManager;
protected WebView actionToWebview = null; protected WebView actionToWebview = null;
protected int fillableFieldCount = 0; protected int fillableFieldCount = 0;
protected IqPacket pendingResponsePacket = null; protected Iq pendingResponsePacket = null;
protected boolean waitingForRefresh = false; protected boolean waitingForRefresh = false;
CommandSession(String title, String node, XmppConnectionService xmppConnectionService) { CommandSession(String title, String node, XmppConnectionService xmppConnectionService) {
@ -2912,7 +2912,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
return mNode; return mNode;
} }
public void updateWithResponse(final IqPacket iq) { public void updateWithResponse(final Iq iq) {
if (getView() != null && getView().isAttachedToWindow()) { if (getView() != null && getView().isAttachedToWindow()) {
getView().post(() -> updateWithResponseUiThread(iq)); getView().post(() -> updateWithResponseUiThread(iq));
} else { } else {
@ -2920,7 +2920,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
} }
} }
protected void updateWithResponseUiThread(final IqPacket iq) { protected void updateWithResponseUiThread(final Iq iq) {
Timer oldTimer = this.loadingTimer; Timer oldTimer = this.loadingTimer;
this.loadingTimer = new Timer(); this.loadingTimer = new Timer();
oldTimer.cancel(); oldTimer.cancel();
@ -2937,7 +2937,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
boolean actionsCleared = false; boolean actionsCleared = false;
Element command = iq.findChild("command", "http://jabber.org/protocol/commands"); Element command = iq.findChild("command", "http://jabber.org/protocol/commands");
if (iq.getType() == IqPacket.TYPE.RESULT && command != null) { if (iq.getType() == Iq.Type.RESULT && command != null) {
if (mNode.equals("jabber:iq:register") && command.getAttribute("status") != null && command.getAttribute("status").equals("completed")) { if (mNode.equals("jabber:iq:register") && command.getAttribute("status") != null && command.getAttribute("status").equals("completed")) {
xmppConnectionService.createContact(getAccount().getRoster().getContact(iq.getFrom()), true); xmppConnectionService.createContact(getAccount().getRoster().getContact(iq.getFrom()), true);
} }
@ -3101,7 +3101,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
public int getItemCount() { public int getItemCount() {
if (loading) return 1; if (loading) return 1;
if (response == null) return 0; if (response == null) return 0;
if (response.getType() == IqPacket.TYPE.RESULT && responseElement != null && responseElement.getNamespace().equals("jabber:x:data")) { if (response.getType() == Iq.Type.RESULT && responseElement != null && responseElement.getNamespace().equals("jabber:x:data")) {
int i = 0; int i = 0;
for (Element el : responseElement.getChildren()) { for (Element el : responseElement.getChildren()) {
if (!el.getNamespace().equals("jabber:x:data")) continue; if (!el.getNamespace().equals("jabber:x:data")) continue;
@ -3134,7 +3134,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
if (items.get(position) != null) return items.get(position); if (items.get(position) != null) return items.get(position);
if (response == null) return null; if (response == null) return null;
if (response.getType() == IqPacket.TYPE.RESULT && responseElement != null) { if (response.getType() == Iq.Type.RESULT && responseElement != null) {
if (responseElement.getNamespace().equals("jabber:x:data")) { if (responseElement.getNamespace().equals("jabber:x:data")) {
int i = 0; int i = 0;
for (Element el : responseElement.getChildren()) { for (Element el : responseElement.getChildren()) {
@ -3317,7 +3317,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
return false; return false;
} }
final IqPacket packet = new IqPacket(IqPacket.TYPE.SET); final var packet = new Iq(Iq.Type.SET);
packet.setTo(response.getFrom()); packet.setTo(response.getFrom());
final Element c = packet.addChild("command", Namespace.COMMANDS); final Element c = packet.addChild("command", Namespace.COMMANDS);
c.setAttribute("node", mNode); c.setAttribute("node", mNode);
@ -3360,7 +3360,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
if (c.getAttribute("action") == null) c.setAttribute("action", action); if (c.getAttribute("action") == null) c.setAttribute("action", action);
executing = true; executing = true;
xmppConnectionService.sendIqPacket(getAccount(), packet, (a, iq) -> { xmppConnectionService.sendIqPacket(getAccount(), packet, (iq) -> {
updateWithResponse(iq); updateWithResponse(iq);
}, 120L); }, 120L);
@ -3495,7 +3495,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
actionsAdapter.notifyDataSetChanged(); actionsAdapter.notifyDataSetChanged();
if (pendingResponsePacket != null) { if (pendingResponsePacket != null) {
final IqPacket pending = pendingResponsePacket; final var pending = pendingResponsePacket;
pendingResponsePacket = null; pendingResponsePacket = null;
updateWithResponseUiThread(pending); updateWithResponseUiThread(pending);
} }
@ -3570,7 +3570,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
} }
@Override @Override
protected void updateWithResponseUiThread(final IqPacket iq) { protected void updateWithResponseUiThread(final Iq iq) {
Timer oldTimer = this.loadingTimer; Timer oldTimer = this.loadingTimer;
this.loadingTimer = new Timer(); this.loadingTimer = new Timer();
oldTimer.cancel(); oldTimer.cancel();
@ -3586,7 +3586,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
layoutManager.setSpanCount(1); layoutManager.setSpanCount(1);
final Element query = iq.findChild("query", "http://jabber.org/protocol/muc#owner"); final Element query = iq.findChild("query", "http://jabber.org/protocol/muc#owner");
if (iq.getType() == IqPacket.TYPE.RESULT && query != null) { if (iq.getType() == Iq.Type.RESULT && query != null) {
final Data form = Data.parse(query.findChild("x", "jabber:x:data")); final Data form = Data.parse(query.findChild("x", "jabber:x:data"));
final String title = form.getTitle(); final String title = form.getTitle();
if (title != null) { if (title != null) {
@ -3605,7 +3605,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
if (actionsAdapter.getPosition("cancel") < 0) { if (actionsAdapter.getPosition("cancel") < 0) {
actionsAdapter.insert(Pair.create("cancel", "cancel"), 0); actionsAdapter.insert(Pair.create("cancel", "cancel"), 0);
} }
} else if (iq.getType() == IqPacket.TYPE.RESULT) { } else if (iq.getType() == Iq.Type.RESULT) {
expectingRemoval = true; expectingRemoval = true;
removeSession(this); removeSession(this);
return; return;
@ -3619,7 +3619,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
@Override @Override
public synchronized boolean execute(String action) { public synchronized boolean execute(String action) {
if ("cancel".equals(action)) { if ("cancel".equals(action)) {
final IqPacket packet = new IqPacket(IqPacket.TYPE.SET); final var packet = new Iq(Iq.Type.SET);
packet.setTo(response.getFrom()); packet.setTo(response.getFrom());
final Element form = packet final Element form = packet
.addChild("query", "http://jabber.org/protocol/muc#owner") .addChild("query", "http://jabber.org/protocol/muc#owner")
@ -3631,7 +3631,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
if (!"save".equals(action)) return true; if (!"save".equals(action)) return true;
final IqPacket packet = new IqPacket(IqPacket.TYPE.SET); final var packet = new Iq(Iq.Type.SET);
packet.setTo(response.getFrom()); packet.setTo(response.getFrom());
String formType = responseElement == null ? null : responseElement.getAttribute("type"); String formType = responseElement == null ? null : responseElement.getAttribute("type");
@ -3647,7 +3647,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
} }
executing = true; executing = true;
xmppConnectionService.sendIqPacket(getAccount(), packet, (a, iq) -> { xmppConnectionService.sendIqPacket(getAccount(), packet, (iq) -> {
updateWithResponse(iq); updateWithResponse(iq);
}, 120L); }, 120L);

View file

@ -29,6 +29,14 @@ import eu.siacs.conversations.xmpp.forms.Field;
import eu.siacs.conversations.xmpp.pep.Avatar; import eu.siacs.conversations.xmpp.pep.Avatar;
import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Element;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
public class MucOptions { public class MucOptions {
public static final String STATUS_CODE_SELF_PRESENCE = "110"; public static final String STATUS_CODE_SELF_PRESENCE = "110";
@ -199,6 +207,11 @@ public class MucOptions {
} }
} }
public boolean allowPmRaw() {
final Field field = getRoomInfoForm().getFieldByName("muc#roomconfig_allowpm");
return field == null || Arrays.asList("anyone","participants").contains(field.getValue());
}
public boolean participating() { public boolean participating() {
return self.getRole().ranks(Role.PARTICIPANT) || !moderated(); return self.getRole().ranks(Role.PARTICIPANT) || !moderated();
} }

View file

@ -24,7 +24,7 @@ import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xml.Namespace;
import eu.siacs.conversations.xmpp.forms.Data; import eu.siacs.conversations.xmpp.forms.Data;
import eu.siacs.conversations.xmpp.forms.Field; import eu.siacs.conversations.xmpp.forms.Field;
import eu.siacs.conversations.xmpp.stanzas.IqPacket; import im.conversations.android.xmpp.model.stanza.Iq;
public class ServiceDiscoveryResult { public class ServiceDiscoveryResult {
public static final String TABLENAME = "discovery_results"; public static final String TABLENAME = "discovery_results";
@ -36,7 +36,7 @@ public class ServiceDiscoveryResult {
protected final List<String> features; protected final List<String> features;
protected final List<Data> forms; protected final List<Data> forms;
private final List<Identity> identities; private final List<Identity> identities;
public ServiceDiscoveryResult(final IqPacket packet) { public ServiceDiscoveryResult(final Iq packet) {
this.identities = new ArrayList<>(); this.identities = new ArrayList<>();
this.features = new ArrayList<>(); this.features = new ArrayList<>();
this.forms = new ArrayList<>(); this.forms = new ArrayList<>();
@ -279,7 +279,7 @@ public class ServiceDiscoveryResult {
return values; return values;
} }
public static class Identity implements Comparable { public static class Identity implements Comparable<Identity> {
protected final String type; protected final String type;
protected final String lang; protected final String lang;
protected final String name; protected final String name;
@ -327,8 +327,21 @@ public class ServiceDiscoveryResult {
return this.name; return this.name;
} }
public int compareTo(@NonNull Object other) { JSONObject toJSON() {
Identity o = (Identity) other; try {
JSONObject o = new JSONObject();
o.put("category", this.getCategory());
o.put("type", this.getType());
o.put("lang", this.getLang());
o.put("name", this.getName());
return o;
} catch (JSONException e) {
return null;
}
}
@Override
public int compareTo(final Identity o) {
int r = blankNull(this.getCategory()).compareTo(blankNull(o.getCategory())); int r = blankNull(this.getCategory()).compareTo(blankNull(o.getCategory()));
if (r == 0) { if (r == 0) {
r = blankNull(this.getType()).compareTo(blankNull(o.getType())); r = blankNull(this.getType()).compareTo(blankNull(o.getType()));
@ -342,18 +355,5 @@ public class ServiceDiscoveryResult {
return r; return r;
} }
JSONObject toJSON() {
try {
JSONObject o = new JSONObject();
o.put("category", this.getCategory());
o.put("type", this.getType());
o.put("lang", this.getLang());
o.put("name", this.getName());
return o;
} catch (JSONException e) {
return null;
}
}
} }
} }

View file

@ -45,7 +45,7 @@ import eu.siacs.conversations.xml.Namespace;
import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.Jid;
import eu.siacs.conversations.xmpp.forms.Data; import eu.siacs.conversations.xmpp.forms.Data;
import eu.siacs.conversations.xmpp.pep.Avatar; import eu.siacs.conversations.xmpp.pep.Avatar;
import eu.siacs.conversations.xmpp.stanzas.IqPacket; import im.conversations.android.xmpp.model.stanza.Iq;
public class IqGenerator extends AbstractGenerator { public class IqGenerator extends AbstractGenerator {
@ -53,8 +53,8 @@ public class IqGenerator extends AbstractGenerator {
super(service); super(service);
} }
public IqPacket discoResponse(final Account account, final IqPacket request) { public Iq discoResponse(final Account account, final Iq request) {
final IqPacket packet = new IqPacket(IqPacket.TYPE.RESULT); final var packet = new Iq(Iq.Type.RESULT);
packet.setId(request.getId()); packet.setId(request.getId());
packet.setTo(request.getFrom()); packet.setTo(request.getFrom());
final Element query = packet.addChild("query", "http://jabber.org/protocol/disco#info"); final Element query = packet.addChild("query", "http://jabber.org/protocol/disco#info");
@ -69,8 +69,8 @@ public class IqGenerator extends AbstractGenerator {
return packet; return packet;
} }
public IqPacket versionResponse(final IqPacket request) { public Iq versionResponse(final Iq request) {
final IqPacket packet = request.generateResponse(IqPacket.TYPE.RESULT); final var packet = request.generateResponse(Iq.Type.RESULT);
Element query = packet.query("jabber:iq:version"); Element query = packet.query("jabber:iq:version");
query.addChild("name").setContent(mXmppConnectionService.getString(R.string.app_name)); query.addChild("name").setContent(mXmppConnectionService.getString(R.string.app_name));
query.addChild("version").setContent(getIdentityVersion()); query.addChild("version").setContent(getIdentityVersion());
@ -93,8 +93,8 @@ public class IqGenerator extends AbstractGenerator {
return packet; return packet;
} }
public IqPacket entityTimeResponse(IqPacket request) { public Iq entityTimeResponse(final Iq request) {
final IqPacket packet = request.generateResponse(IqPacket.TYPE.RESULT); final Iq packet = request.generateResponse(Iq.Type.RESULT);
Element time = packet.addChild("time", "urn:xmpp:time"); Element time = packet.addChild("time", "urn:xmpp:time");
final long now = System.currentTimeMillis(); final long now = System.currentTimeMillis();
time.addChild("utc").setContent(getTimestamp(now)); time.addChild("utc").setContent(getTimestamp(now));
@ -113,14 +113,14 @@ public class IqGenerator extends AbstractGenerator {
return packet; return packet;
} }
public IqPacket purgeOfflineMessages() { public static Iq purgeOfflineMessages() {
final IqPacket packet = new IqPacket(IqPacket.TYPE.SET); final Iq packet = new Iq(Iq.Type.SET);
packet.addChild("offline", Namespace.FLEXIBLE_OFFLINE_MESSAGE_RETRIEVAL).addChild("purge"); packet.addChild("offline", Namespace.FLEXIBLE_OFFLINE_MESSAGE_RETRIEVAL).addChild("purge");
return packet; return packet;
} }
protected IqPacket publish(final String node, final Element item, final Bundle options) { protected Iq publish(final String node, final Element item, final Bundle options) {
final IqPacket packet = new IqPacket(IqPacket.TYPE.SET); final var packet = new Iq(Iq.Type.SET);
final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB); final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB);
final Element publish = pubsub.addChild("publish"); final Element publish = pubsub.addChild("publish");
publish.setAttribute("node", node); publish.setAttribute("node", node);
@ -132,12 +132,12 @@ public class IqGenerator extends AbstractGenerator {
return packet; return packet;
} }
protected IqPacket publish(final String node, final Element item) { protected Iq publish(final String node, final Element item) {
return publish(node, item, null); return publish(node, item, null);
} }
private IqPacket retrieve(String node, Element item) { private Iq retrieve(String node, Element item) {
final IqPacket packet = new IqPacket(IqPacket.TYPE.GET); final var packet = new Iq(Iq.Type.GET);
final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB); final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB);
final Element items = pubsub.addChild("items"); final Element items = pubsub.addChild("items");
items.setAttribute("node", node); items.setAttribute("node", node);
@ -147,36 +147,36 @@ public class IqGenerator extends AbstractGenerator {
return packet; return packet;
} }
public IqPacket retrieveVcard4(final Jid jid) { public Iq retrieveVcard4(final Jid jid) {
final IqPacket packet = retrieve("urn:xmpp:vcard4", null); final var packet = retrieve("urn:xmpp:vcard4", null);
packet.setTo(jid); packet.setTo(jid);
return packet; return packet;
} }
public IqPacket retrieveBookmarks() { public Iq retrieveBookmarks() {
return retrieve(Namespace.BOOKMARKS2, null); return retrieve(Namespace.BOOKMARKS2, null);
} }
public IqPacket retrieveMds() { public Iq retrieveMds() {
return retrieve(Namespace.MDS_DISPLAYED, null); return retrieve(Namespace.MDS_DISPLAYED, null);
} }
public IqPacket publishNick(String nick) { public Iq publishNick(String nick) {
final Element item = new Element("item"); final Element item = new Element("item");
item.setAttribute("id", "current"); item.setAttribute("id", "current");
item.addChild("nick", Namespace.NICK).setContent(nick); item.addChild("nick", Namespace.NICK).setContent(nick);
return publish(Namespace.NICK, item); return publish(Namespace.NICK, item);
} }
public IqPacket deleteNode(final String node) { public Iq deleteNode(final String node) {
final IqPacket packet = new IqPacket(IqPacket.TYPE.SET); final var packet = new Iq(Iq.Type.SET);
final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB_OWNER); final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB_OWNER);
pubsub.addChild("delete").setAttribute("node", node); pubsub.addChild("delete").setAttribute("node", node);
return packet; return packet;
} }
public IqPacket deleteItem(final String node, final String id) { public Iq deleteItem(final String node, final String id) {
IqPacket packet = new IqPacket(IqPacket.TYPE.SET); final var packet = new Iq(Iq.Type.SET);
final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB); final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB);
final Element retract = pubsub.addChild("retract"); final Element retract = pubsub.addChild("retract");
retract.setAttribute("node", node); retract.setAttribute("node", node);
@ -185,7 +185,7 @@ public class IqGenerator extends AbstractGenerator {
return packet; return packet;
} }
public IqPacket publishAvatar(Avatar avatar, Bundle options) { public Iq publishAvatar(Avatar avatar, Bundle options) {
final Element item = new Element("item"); final Element item = new Element("item");
item.setAttribute("id", avatar.sha1sum); item.setAttribute("id", avatar.sha1sum);
final Element data = item.addChild("data", Namespace.AVATAR_DATA); final Element data = item.addChild("data", Namespace.AVATAR_DATA);
@ -193,14 +193,14 @@ public class IqGenerator extends AbstractGenerator {
return publish(Namespace.AVATAR_DATA, item, options); return publish(Namespace.AVATAR_DATA, item, options);
} }
public IqPacket publishElement(final String namespace, final Element element, String id, final Bundle options) { public Iq publishElement(final String namespace, final Element element, String id, final Bundle options) {
final Element item = new Element("item"); final Element item = new Element("item");
item.setAttribute("id", id); item.setAttribute("id", id);
item.addChild(element); item.addChild(element);
return publish(namespace, item, options); return publish(namespace, item, options);
} }
public IqPacket publishAvatarMetadata(final Avatar avatar, final Bundle options) { public Iq publishAvatarMetadata(final Avatar avatar, final Bundle options) {
final Element item = new Element("item"); final Element item = new Element("item");
item.setAttribute("id", avatar.sha1sum); item.setAttribute("id", avatar.sha1sum);
final Element metadata = item final Element metadata = item
@ -214,57 +214,57 @@ public class IqGenerator extends AbstractGenerator {
return publish(Namespace.AVATAR_METADATA, item, options); return publish(Namespace.AVATAR_METADATA, item, options);
} }
public IqPacket retrievePepAvatar(final Avatar avatar) { public Iq retrievePepAvatar(final Avatar avatar) {
final Element item = new Element("item"); final Element item = new Element("item");
item.setAttribute("id", avatar.sha1sum); item.setAttribute("id", avatar.sha1sum);
final IqPacket packet = retrieve(Namespace.AVATAR_DATA, item); final var packet = retrieve(Namespace.AVATAR_DATA, item);
packet.setTo(avatar.owner); packet.setTo(avatar.owner);
return packet; return packet;
} }
public IqPacket retrieveVcardAvatar(final Avatar avatar) { public Iq retrieveVcardAvatar(final Avatar avatar) {
final IqPacket packet = new IqPacket(IqPacket.TYPE.GET); final Iq packet = new Iq(Iq.Type.GET);
packet.setTo(avatar.owner); packet.setTo(avatar.owner);
packet.addChild("vCard", "vcard-temp"); packet.addChild("vCard", "vcard-temp");
return packet; return packet;
} }
public IqPacket retrieveVcardAvatar(final Jid to) { public Iq retrieveVcardAvatar(final Jid to) {
final IqPacket packet = new IqPacket(IqPacket.TYPE.GET); final Iq packet = new Iq(Iq.Type.GET);
packet.setTo(to); packet.setTo(to);
packet.addChild("vCard", "vcard-temp"); packet.addChild("vCard", "vcard-temp");
return packet; return packet;
} }
public IqPacket retrieveAvatarMetaData(final Jid to) { public Iq retrieveAvatarMetaData(final Jid to) {
final IqPacket packet = retrieve("urn:xmpp:avatar:metadata", null); final Iq packet = retrieve("urn:xmpp:avatar:metadata", null);
if (to != null) { if (to != null) {
packet.setTo(to); packet.setTo(to);
} }
return packet; return packet;
} }
public IqPacket retrieveDeviceIds(final Jid to) { public Iq retrieveDeviceIds(final Jid to) {
final IqPacket packet = retrieve(AxolotlService.PEP_DEVICE_LIST, null); final var packet = retrieve(AxolotlService.PEP_DEVICE_LIST, null);
if (to != null) { if (to != null) {
packet.setTo(to); packet.setTo(to);
} }
return packet; return packet;
} }
public IqPacket retrieveBundlesForDevice(final Jid to, final int deviceid) { public Iq retrieveBundlesForDevice(final Jid to, final int deviceid) {
final IqPacket packet = retrieve(AxolotlService.PEP_BUNDLES + ":" + deviceid, null); final var packet = retrieve(AxolotlService.PEP_BUNDLES + ":" + deviceid, null);
packet.setTo(to); packet.setTo(to);
return packet; return packet;
} }
public IqPacket retrieveVerificationForDevice(final Jid to, final int deviceid) { public Iq retrieveVerificationForDevice(final Jid to, final int deviceid) {
final IqPacket packet = retrieve(AxolotlService.PEP_VERIFICATION + ":" + deviceid, null); final var packet = retrieve(AxolotlService.PEP_VERIFICATION + ":" + deviceid, null);
packet.setTo(to); packet.setTo(to);
return packet; return packet;
} }
public IqPacket publishDeviceIds(final Set<Integer> ids, final Bundle publishOptions) { public Iq publishDeviceIds(final Set<Integer> ids, final Bundle publishOptions) {
final Element item = new Element("item"); final Element item = new Element("item");
item.setAttribute("id", "current"); item.setAttribute("id", "current");
final Element list = item.addChild("list", AxolotlService.PEP_PREFIX); final Element list = item.addChild("list", AxolotlService.PEP_PREFIX);
@ -314,7 +314,7 @@ public class IqGenerator extends AbstractGenerator {
return displayed; return displayed;
} }
public IqPacket publishBundles(final SignedPreKeyRecord signedPreKeyRecord, final IdentityKey identityKey, public Iq publishBundles(final SignedPreKeyRecord signedPreKeyRecord, final IdentityKey identityKey,
final Set<PreKeyRecord> preKeyRecords, final int deviceId, Bundle publishOptions) { final Set<PreKeyRecord> preKeyRecords, final int deviceId, Bundle publishOptions) {
final Element item = new Element("item"); final Element item = new Element("item");
item.setAttribute("id", "current"); item.setAttribute("id", "current");
@ -338,7 +338,7 @@ public class IqGenerator extends AbstractGenerator {
return publish(AxolotlService.PEP_BUNDLES + ":" + deviceId, item, publishOptions); return publish(AxolotlService.PEP_BUNDLES + ":" + deviceId, item, publishOptions);
} }
public IqPacket publishVerification(byte[] signature, X509Certificate[] certificates, final int deviceId) { public Iq publishVerification(byte[] signature, X509Certificate[] certificates, final int deviceId) {
final Element item = new Element("item"); final Element item = new Element("item");
item.setAttribute("id", "current"); item.setAttribute("id", "current");
final Element verification = item.addChild("verification", AxolotlService.PEP_PREFIX); final Element verification = item.addChild("verification", AxolotlService.PEP_PREFIX);
@ -356,8 +356,8 @@ public class IqGenerator extends AbstractGenerator {
return publish(AxolotlService.PEP_VERIFICATION + ":" + deviceId, item); return publish(AxolotlService.PEP_VERIFICATION + ":" + deviceId, item);
} }
public IqPacket queryMessageArchiveManagement(final MessageArchiveService.Query mam) { public Iq queryMessageArchiveManagement(final MessageArchiveService.Query mam) {
final IqPacket packet = new IqPacket(IqPacket.TYPE.SET); final Iq packet = new Iq(Iq.Type.SET);
final Element query = packet.query(mam.version.namespace); final Element query = packet.query(mam.version.namespace);
query.setAttribute("queryid", mam.getQueryId()); query.setAttribute("queryid", mam.getQueryId());
final Data data = new Data(); final Data data = new Data();
@ -387,15 +387,15 @@ public class IqGenerator extends AbstractGenerator {
return packet; return packet;
} }
public IqPacket generateGetBlockList() { public Iq generateGetBlockList() {
final IqPacket iq = new IqPacket(IqPacket.TYPE.GET); final Iq iq = new Iq(Iq.Type.GET);
iq.addChild("blocklist", Namespace.BLOCKING); iq.addChild("blocklist", Namespace.BLOCKING);
return iq; return iq;
} }
public IqPacket generateSetBlockRequest(final Jid jid, final boolean reportSpam, final String serverMsgId) { public Iq generateSetBlockRequest(final Jid jid, final boolean reportSpam, final String serverMsgId) {
final IqPacket iq = new IqPacket(IqPacket.TYPE.SET); final Iq iq = new Iq(Iq.Type.SET);
final Element block = iq.addChild("block", Namespace.BLOCKING); final Element block = iq.addChild("block", Namespace.BLOCKING);
final Element item = block.addChild("item").setAttribute("jid", jid); final Element item = block.addChild("item").setAttribute("jid", jid);
if (reportSpam) { if (reportSpam) {
@ -411,15 +411,15 @@ public class IqGenerator extends AbstractGenerator {
return iq; return iq;
} }
public IqPacket generateSetUnblockRequest(final Jid jid) { public Iq generateSetUnblockRequest(final Jid jid) {
final IqPacket iq = new IqPacket(IqPacket.TYPE.SET); final Iq iq = new Iq(Iq.Type.SET);
final Element block = iq.addChild("unblock", Namespace.BLOCKING); final Element block = iq.addChild("unblock", Namespace.BLOCKING);
block.addChild("item").setAttribute("jid", jid); block.addChild("item").setAttribute("jid", jid);
return iq; return iq;
} }
public IqPacket generateSetPassword(final Account account, final String newPassword) { public Iq generateSetPassword(final Account account, final String newPassword) {
final IqPacket packet = new IqPacket(IqPacket.TYPE.SET); final Iq packet = new Iq(Iq.Type.SET);
packet.setTo(account.getDomain()); packet.setTo(account.getDomain());
final Element query = packet.addChild("query", Namespace.REGISTER); final Element query = packet.addChild("query", Namespace.REGISTER);
final Jid jid = account.getJid(); final Jid jid = account.getJid();
@ -428,14 +428,14 @@ public class IqGenerator extends AbstractGenerator {
return packet; return packet;
} }
public IqPacket changeAffiliation(Conversation conference, Jid jid, String affiliation) { public Iq changeAffiliation(Conversation conference, Jid jid, String affiliation) {
List<Jid> jids = new ArrayList<>(); List<Jid> jids = new ArrayList<>();
jids.add(jid); jids.add(jid);
return changeAffiliation(conference, jids, affiliation); return changeAffiliation(conference, jids, affiliation);
} }
public IqPacket changeAffiliation(Conversation conference, List<Jid> jids, String affiliation) { public Iq changeAffiliation(Conversation conference, List<Jid> jids, String affiliation) {
IqPacket packet = new IqPacket(IqPacket.TYPE.SET); final Iq packet = new Iq(Iq.Type.SET);
packet.setTo(conference.getJid().asBareJid()); packet.setTo(conference.getJid().asBareJid());
packet.setFrom(conference.getAccount().getJid()); packet.setFrom(conference.getAccount().getJid());
Element query = packet.query("http://jabber.org/protocol/muc#admin"); Element query = packet.query("http://jabber.org/protocol/muc#admin");
@ -447,8 +447,8 @@ public class IqGenerator extends AbstractGenerator {
return packet; return packet;
} }
public IqPacket changeRole(Conversation conference, String nick, String role) { public Iq changeRole(Conversation conference, String nick, String role) {
IqPacket packet = new IqPacket(IqPacket.TYPE.SET); final Iq packet = new Iq(Iq.Type.SET);
packet.setTo(conference.getJid().asBareJid()); packet.setTo(conference.getJid().asBareJid());
packet.setFrom(conference.getAccount().getJid()); packet.setFrom(conference.getAccount().getJid());
Element item = packet.query("http://jabber.org/protocol/muc#admin").addChild("item"); Element item = packet.query("http://jabber.org/protocol/muc#admin").addChild("item");
@ -457,11 +457,11 @@ public class IqGenerator extends AbstractGenerator {
return packet; return packet;
} }
public IqPacket moderateMessage(Account account, Message m, String reason) { public Iq moderateMessage(Account account, Message m, String reason) {
IqPacket packet = new IqPacket(IqPacket.TYPE.SET); final var packet = new Iq(Iq.Type.SET);
packet.setTo(m.getConversation().getJid().asBareJid()); packet.setTo(m.getConversation().getJid().asBareJid());
packet.setFrom(account.getJid()); packet.setFrom(account.getJid());
Element moderate = final var moderate =
packet.addChild("apply-to", "urn:xmpp:fasten:0") packet.addChild("apply-to", "urn:xmpp:fasten:0")
.setAttribute("id", m.getServerMsgId()) .setAttribute("id", m.getServerMsgId())
.addChild("moderate", "urn:xmpp:message-moderate:0"); .addChild("moderate", "urn:xmpp:message-moderate:0");
@ -470,8 +470,8 @@ public class IqGenerator extends AbstractGenerator {
return packet; return packet;
} }
public IqPacket requestHttpUploadSlot(Jid host, DownloadableFile file, String name, String mime) { public Iq requestHttpUploadSlot(Jid host, DownloadableFile file, String name, String mime) {
IqPacket packet = new IqPacket(IqPacket.TYPE.GET); final Iq packet = new Iq(Iq.Type.GET);
packet.setTo(host); packet.setTo(host);
Element request = packet.addChild("request", Namespace.HTTP_UPLOAD); Element request = packet.addChild("request", Namespace.HTTP_UPLOAD);
request.setAttribute("filename", name == null ? convertFilename(file.getName()) : name); request.setAttribute("filename", name == null ? convertFilename(file.getName()) : name);
@ -480,8 +480,8 @@ public class IqGenerator extends AbstractGenerator {
return packet; return packet;
} }
public IqPacket requestHttpUploadLegacySlot(Jid host, DownloadableFile file, String mime) { public Iq requestHttpUploadLegacySlot(Jid host, DownloadableFile file, String mime) {
IqPacket packet = new IqPacket(IqPacket.TYPE.GET); final Iq packet = new Iq(Iq.Type.GET);
packet.setTo(host); packet.setTo(host);
Element request = packet.addChild("request", Namespace.HTTP_UPLOAD_LEGACY); Element request = packet.addChild("request", Namespace.HTTP_UPLOAD_LEGACY);
request.addChild("filename").setContent(convertFilename(file.getName())); request.addChild("filename").setContent(convertFilename(file.getName()));
@ -507,8 +507,8 @@ public class IqGenerator extends AbstractGenerator {
} }
} }
public IqPacket generateCreateAccountWithCaptcha(Account account, String id, Data data) { public static Iq generateCreateAccountWithCaptcha(final Account account, final String id, final Data data) {
final IqPacket register = new IqPacket(IqPacket.TYPE.SET); final Iq register = new Iq(Iq.Type.SET);
register.setFrom(account.getJid().asBareJid()); register.setFrom(account.getJid().asBareJid());
register.setTo(account.getDomain()); register.setTo(account.getDomain());
register.setId(id); register.setId(id);
@ -519,12 +519,12 @@ public class IqGenerator extends AbstractGenerator {
return register; return register;
} }
public IqPacket pushTokenToAppServer(Jid appServer, String token, String deviceId) { public Iq pushTokenToAppServer(Jid appServer, String token, String deviceId) {
return pushTokenToAppServer(appServer, token, deviceId, null); return pushTokenToAppServer(appServer, token, deviceId, null);
} }
public IqPacket pushTokenToAppServer(Jid appServer, String token, String deviceId, Jid muc) { public Iq pushTokenToAppServer(Jid appServer, String token, String deviceId, Jid muc) {
final IqPacket packet = new IqPacket(IqPacket.TYPE.SET); final Iq packet = new Iq(Iq.Type.SET);
packet.setTo(appServer); packet.setTo(appServer);
final Element command = packet.addChild("command", Namespace.COMMANDS); final Element command = packet.addChild("command", Namespace.COMMANDS);
command.setAttribute("node", "register-push-fcm"); command.setAttribute("node", "register-push-fcm");
@ -540,8 +540,8 @@ public class IqGenerator extends AbstractGenerator {
return packet; return packet;
} }
public IqPacket unregisterChannelOnAppServer(Jid appServer, String deviceId, String channel) { public Iq unregisterChannelOnAppServer(Jid appServer, String deviceId, String channel) {
final IqPacket packet = new IqPacket(IqPacket.TYPE.SET); final Iq packet = new Iq(Iq.Type.SET);
packet.setTo(appServer); packet.setTo(appServer);
final Element command = packet.addChild("command", Namespace.COMMANDS); final Element command = packet.addChild("command", Namespace.COMMANDS);
command.setAttribute("node", "unregister-push-fcm"); command.setAttribute("node", "unregister-push-fcm");
@ -554,8 +554,8 @@ public class IqGenerator extends AbstractGenerator {
return packet; return packet;
} }
public IqPacket enablePush(final Jid jid, final String node, final String secret) { public Iq enablePush(final Jid jid, final String node, final String secret) {
IqPacket packet = new IqPacket(IqPacket.TYPE.SET); final Iq packet = new Iq(Iq.Type.SET);
Element enable = packet.addChild("enable", Namespace.PUSH); Element enable = packet.addChild("enable", Namespace.PUSH);
enable.setAttribute("jid", jid); enable.setAttribute("jid", jid);
enable.setAttribute("node", node); enable.setAttribute("node", node);
@ -569,16 +569,16 @@ public class IqGenerator extends AbstractGenerator {
return packet; return packet;
} }
public IqPacket disablePush(final Jid jid, final String node) { public Iq disablePush(final Jid jid, final String node) {
IqPacket packet = new IqPacket(IqPacket.TYPE.SET); Iq packet = new Iq(Iq.Type.SET);
Element disable = packet.addChild("disable", Namespace.PUSH); Element disable = packet.addChild("disable", Namespace.PUSH);
disable.setAttribute("jid", jid); disable.setAttribute("jid", jid);
disable.setAttribute("node", node); disable.setAttribute("node", node);
return packet; return packet;
} }
public IqPacket queryAffiliation(Conversation conversation, String affiliation) { public Iq queryAffiliation(Conversation conversation, String affiliation) {
IqPacket packet = new IqPacket(IqPacket.TYPE.GET); final Iq packet = new Iq(Iq.Type.GET);
packet.setTo(conversation.getJid().asBareJid()); packet.setTo(conversation.getJid().asBareJid());
packet.query("http://jabber.org/protocol/muc#admin").addChild("item").setAttribute("affiliation", affiliation); packet.query("http://jabber.org/protocol/muc#admin").addChild("item").setAttribute("affiliation", affiliation);
return packet; return packet;
@ -611,16 +611,16 @@ public class IqGenerator extends AbstractGenerator {
return options; return options;
} }
public IqPacket requestPubsubConfiguration(Jid jid, String node) { public Iq requestPubsubConfiguration(Jid jid, String node) {
return pubsubConfiguration(jid, node, null); return pubsubConfiguration(jid, node, null);
} }
public IqPacket publishPubsubConfiguration(Jid jid, String node, Data data) { public Iq publishPubsubConfiguration(Jid jid, String node, Data data) {
return pubsubConfiguration(jid, node, data); return pubsubConfiguration(jid, node, data);
} }
private IqPacket pubsubConfiguration(Jid jid, String node, Data data) { private Iq pubsubConfiguration(Jid jid, String node, Data data) {
IqPacket packet = new IqPacket(data == null ? IqPacket.TYPE.GET : IqPacket.TYPE.SET); final Iq packet = new Iq(data == null ? Iq.Type.GET : Iq.Type.SET);
packet.setTo(jid); packet.setTo(jid);
Element pubsub = packet.addChild("pubsub", "http://jabber.org/protocol/pubsub#owner"); Element pubsub = packet.addChild("pubsub", "http://jabber.org/protocol/pubsub#owner");
Element configure = pubsub.addChild("configure").setAttribute("node", node); Element configure = pubsub.addChild("configure").setAttribute("node", node);
@ -630,43 +630,43 @@ public class IqGenerator extends AbstractGenerator {
return packet; return packet;
} }
public IqPacket queryDiscoItems(Jid jid) { public Iq queryDiscoItems(final Jid jid) {
IqPacket packet = new IqPacket(IqPacket.TYPE.GET); final Iq packet = new Iq(Iq.Type.GET);
packet.setTo(jid); packet.setTo(jid);
packet.query(Namespace.DISCO_ITEMS); packet.query(Namespace.DISCO_ITEMS);
return packet; return packet;
} }
public IqPacket queryDiscoItems(Jid jid, String node) { public Iq queryDiscoItems(Jid jid, String node) {
IqPacket packet = queryDiscoItems(jid); final var packet = queryDiscoItems(jid);
final Element query = packet.query(Namespace.DISCO_ITEMS); final var query = packet.query(Namespace.DISCO_ITEMS);
query.setAttribute("node", node); query.setAttribute("node", node);
return packet; return packet;
} }
public IqPacket queryDiscoInfo(Jid jid) { public Iq queryDiscoInfo(final Jid jid) {
IqPacket packet = new IqPacket(IqPacket.TYPE.GET); final Iq packet = new Iq(Iq.Type.GET);
packet.setTo(jid); packet.setTo(jid);
packet.addChild("query",Namespace.DISCO_INFO); packet.addChild("query",Namespace.DISCO_INFO);
return packet; return packet;
} }
public IqPacket bobResponse(IqPacket request) { public Iq bobResponse(Iq request) {
try { try {
String bobCid = request.findChild("data", "urn:xmpp:bob").getAttribute("cid"); final var bobCid = request.findChild("data", "urn:xmpp:bob").getAttribute("cid");
Cid cid = BobTransfer.cid(bobCid); final var cid = BobTransfer.cid(bobCid);
DownloadableFile f = mXmppConnectionService.getFileForCid(cid); final var f = mXmppConnectionService.getFileForCid(cid);
if (f == null || !f.canRead()) { if (f == null || !f.canRead()) {
throw new IOException("No such file"); throw new IOException("No such file");
} else if (f.getSize() > 129000) { } else if (f.getSize() > 129000) {
final IqPacket response = request.generateResponse(IqPacket.TYPE.ERROR); final var response = request.generateResponse(Iq.Type.ERROR);
final Element error = response.addChild("error"); final var error = response.addChild("error");
error.setAttribute("type", "cancel"); error.setAttribute("type", "cancel");
error.addChild("policy-violation", "urn:ietf:params:xml:ns:xmpp-stanzas"); error.addChild("policy-violation", "urn:ietf:params:xml:ns:xmpp-stanzas");
return response; return response;
} else { } else {
final IqPacket response = request.generateResponse(IqPacket.TYPE.RESULT); final var response = request.generateResponse(Iq.Type.RESULT);
final Element data = response.addChild("data", "urn:xmpp:bob"); final var data = response.addChild("data", "urn:xmpp:bob");
data.setAttribute("cid", bobCid); data.setAttribute("cid", bobCid);
data.setAttribute("type", f.getMimeType()); data.setAttribute("type", f.getMimeType());
ByteArrayOutputStream b64 = new ByteArrayOutputStream((int) f.getSize() * 2); ByteArrayOutputStream b64 = new ByteArrayOutputStream((int) f.getSize() * 2);
@ -678,8 +678,8 @@ public class IqGenerator extends AbstractGenerator {
return response; return response;
} }
} catch (final IOException | IllegalStateException e) { } catch (final IOException | IllegalStateException e) {
final IqPacket response = request.generateResponse(IqPacket.TYPE.ERROR); final var response = request.generateResponse(Iq.Type.ERROR);
final Element error = response.addChild("error"); final var error = response.addChild("error");
error.setAttribute("type", "cancel"); error.setAttribute("type", "cancel");
error.addChild("item-not-found", "urn:ietf:params:xml:ns:xmpp-stanzas"); error.addChild("item-not-found", "urn:ietf:params:xml:ns:xmpp-stanzas");
return response; return response;

View file

@ -23,7 +23,6 @@ import eu.siacs.conversations.xmpp.jingle.JingleConnectionManager;
import eu.siacs.conversations.xmpp.jingle.JingleRtpConnection; import eu.siacs.conversations.xmpp.jingle.JingleRtpConnection;
import eu.siacs.conversations.xmpp.jingle.Media; import eu.siacs.conversations.xmpp.jingle.Media;
import eu.siacs.conversations.xmpp.jingle.stanzas.Reason; import eu.siacs.conversations.xmpp.jingle.stanzas.Reason;
import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
public class MessageGenerator extends AbstractGenerator { public class MessageGenerator extends AbstractGenerator {
private static final String OMEMO_FALLBACK_MESSAGE = "I sent you an OMEMO encrypted message but your client doesnt seem to support that. Find more information on https://conversations.im/omemo"; private static final String OMEMO_FALLBACK_MESSAGE = "I sent you an OMEMO encrypted message but your client doesnt seem to support that. Find more information on https://conversations.im/omemo";
@ -33,25 +32,25 @@ public class MessageGenerator extends AbstractGenerator {
super(service); super(service);
} }
private MessagePacket preparePacket(Message message, boolean legacyEncryption) { private im.conversations.android.xmpp.model.stanza.Message preparePacket(Message message, boolean legacyEncryption) {
Conversation conversation = (Conversation) message.getConversation(); Conversation conversation = (Conversation) message.getConversation();
Account account = conversation.getAccount(); Account account = conversation.getAccount();
MessagePacket packet = new MessagePacket(); im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message();
final boolean isWithSelf = conversation.getContact().isSelf(); final boolean isWithSelf = conversation.getContact().isSelf();
if (conversation.getMode() == Conversation.MODE_SINGLE) { if (conversation.getMode() == Conversation.MODE_SINGLE) {
packet.setTo(message.getCounterpart()); packet.setTo(message.getCounterpart());
packet.setType(MessagePacket.TYPE_CHAT); packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.CHAT);
if (!isWithSelf) { if (!isWithSelf) {
packet.addChild("request", "urn:xmpp:receipts"); packet.addChild("request", "urn:xmpp:receipts");
} }
} else if (message.isPrivateMessage()) { } else if (message.isPrivateMessage()) {
packet.setTo(message.getCounterpart()); packet.setTo(message.getCounterpart());
packet.setType(MessagePacket.TYPE_CHAT); packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.CHAT);
packet.addChild("x", "http://jabber.org/protocol/muc#user"); packet.addChild("x", "http://jabber.org/protocol/muc#user");
packet.addChild("request", "urn:xmpp:receipts"); packet.addChild("request", "urn:xmpp:receipts");
} else { } else {
packet.setTo(message.getCounterpart().asBareJid()); packet.setTo(message.getCounterpart().asBareJid());
packet.setType(MessagePacket.TYPE_GROUPCHAT); packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.GROUPCHAT);
} }
if (conversation.isSingleOrPrivateAndNonAnonymous() && !message.isPrivateMessage()) { if (conversation.isSingleOrPrivateAndNonAnonymous() && !message.isPrivateMessage()) {
packet.addChild("markable", "urn:xmpp:chat-markers:0"); packet.addChild("markable", "urn:xmpp:chat-markers:0");
@ -78,7 +77,7 @@ public class MessageGenerator extends AbstractGenerator {
return packet; return packet;
} }
public void addDelay(MessagePacket packet, long timestamp) { public void addDelay(im.conversations.android.xmpp.model.stanza.Message packet, long timestamp) {
final SimpleDateFormat mDateFormat = new SimpleDateFormat( final SimpleDateFormat mDateFormat = new SimpleDateFormat(
"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US); "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US);
mDateFormat.setTimeZone(TimeZone.getTimeZone("UTC")); mDateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
@ -87,8 +86,8 @@ public class MessageGenerator extends AbstractGenerator {
delay.setAttribute("stamp", mDateFormat.format(date)); delay.setAttribute("stamp", mDateFormat.format(date));
} }
public MessagePacket generateAxolotlChat(Message message, XmppAxolotlMessage axolotlMessage) { public im.conversations.android.xmpp.model.stanza.Message generateAxolotlChat(Message message, XmppAxolotlMessage axolotlMessage) {
MessagePacket packet = preparePacket(message, true); im.conversations.android.xmpp.model.stanza.Message packet = preparePacket(message, true);
if (axolotlMessage == null) { if (axolotlMessage == null) {
return null; return null;
} }
@ -101,17 +100,18 @@ public class MessageGenerator extends AbstractGenerator {
return packet; return packet;
} }
public MessagePacket generateKeyTransportMessage(Jid to, XmppAxolotlMessage axolotlMessage) { public im.conversations.android.xmpp.model.stanza.Message generateKeyTransportMessage(Jid to, XmppAxolotlMessage axolotlMessage) {
MessagePacket packet = new MessagePacket(); im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message();
packet.setType(MessagePacket.TYPE_CHAT); packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.CHAT);
packet.setTo(to); packet.setTo(to);
packet.setAxolotlMessage(axolotlMessage.toElement()); packet.setAxolotlMessage(axolotlMessage.toElement());
packet.addChild("store", "urn:xmpp:hints"); packet.addChild("store", "urn:xmpp:hints");
return packet; return packet;
} }
public MessagePacket generateChat(Message message) { public im.conversations.android.xmpp.model.stanza.Message generateChat(Message message) {
MessagePacket packet = preparePacket(message, false); im.conversations.android.xmpp.model.stanza.Message packet = preparePacket(message, false);
String content;
if (message.hasFileOnRemoteHost()) { if (message.hasFileOnRemoteHost()) {
final Message.FileParams fileParams = message.getFileParams(); final Message.FileParams fileParams = message.getFileParams();
@ -139,8 +139,8 @@ public class MessageGenerator extends AbstractGenerator {
return packet; return packet;
} }
public MessagePacket generatePgpChat(Message message) { public im.conversations.android.xmpp.model.stanza.Message generatePgpChat(Message message) {
MessagePacket packet = preparePacket(message, true); final im.conversations.android.xmpp.model.stanza.Message packet = preparePacket(message, true);
if (message.hasFileOnRemoteHost()) { if (message.hasFileOnRemoteHost()) {
Message.FileParams fileParams = message.getFileParams(); Message.FileParams fileParams = message.getFileParams();
final String url = fileParams.url; final String url = fileParams.url;
@ -163,10 +163,10 @@ public class MessageGenerator extends AbstractGenerator {
return packet; return packet;
} }
public MessagePacket generateChatState(Conversation conversation) { public im.conversations.android.xmpp.model.stanza.Message generateChatState(Conversation conversation) {
final Account account = conversation.getAccount(); final Account account = conversation.getAccount();
MessagePacket packet = new MessagePacket(); final im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message();
packet.setType(conversation.getMode() == Conversation.MODE_MULTI ? MessagePacket.TYPE_GROUPCHAT : MessagePacket.TYPE_CHAT); packet.setType(conversation.getMode() == Conversation.MODE_MULTI ? im.conversations.android.xmpp.model.stanza.Message.Type.GROUPCHAT : im.conversations.android.xmpp.model.stanza.Message.Type.CHAT);
packet.setTo(conversation.getJid().asBareJid()); packet.setTo(conversation.getJid().asBareJid());
packet.setFrom(account.getJid()); packet.setFrom(account.getJid());
packet.addChild(ChatState.toElement(conversation.getOutgoingChatState())); packet.addChild(ChatState.toElement(conversation.getOutgoingChatState()));
@ -175,11 +175,11 @@ public class MessageGenerator extends AbstractGenerator {
return packet; return packet;
} }
public MessagePacket confirm(final Message message) { public im.conversations.android.xmpp.model.stanza.Message confirm(final Message message) {
final boolean groupChat = message.getConversation().getMode() == Conversational.MODE_MULTI; final boolean groupChat = message.getConversation().getMode() == Conversational.MODE_MULTI;
final Jid to = message.getCounterpart(); final Jid to = message.getCounterpart();
final MessagePacket packet = new MessagePacket(); final im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message();
packet.setType(groupChat ? MessagePacket.TYPE_GROUPCHAT : MessagePacket.TYPE_CHAT); packet.setType(groupChat ? im.conversations.android.xmpp.model.stanza.Message.Type.GROUPCHAT : im.conversations.android.xmpp.model.stanza.Message.Type.CHAT);
packet.setTo(groupChat ? to.asBareJid() : to); packet.setTo(groupChat ? to.asBareJid() : to);
final Element displayed = packet.addChild("displayed", "urn:xmpp:chat-markers:0"); final Element displayed = packet.addChild("displayed", "urn:xmpp:chat-markers:0");
if (groupChat) { if (groupChat) {
@ -197,20 +197,20 @@ public class MessageGenerator extends AbstractGenerator {
return packet; return packet;
} }
public MessagePacket conferenceSubject(Conversation conversation, String subject) { public im.conversations.android.xmpp.model.stanza.Message conferenceSubject(Conversation conversation, String subject) {
MessagePacket packet = new MessagePacket(); im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message();
packet.setType(MessagePacket.TYPE_GROUPCHAT); packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.GROUPCHAT);
packet.setTo(conversation.getJid().asBareJid()); packet.setTo(conversation.getJid().asBareJid());
packet.addChild("subject").setContent(subject); packet.addChild("subject").setContent(subject);
packet.setFrom(conversation.getAccount().getJid().asBareJid()); packet.setFrom(conversation.getAccount().getJid().asBareJid());
return packet; return packet;
} }
public MessagePacket requestVoice(Jid jid) { public im.conversations.android.xmpp.model.stanza.Message requestVoice(Jid jid) {
MessagePacket packet = new MessagePacket(); final var packet = new im.conversations.android.xmpp.model.stanza.Message();
packet.setType(MessagePacket.TYPE_NORMAL); packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.NORMAL);
packet.setTo(jid.asBareJid()); packet.setTo(jid.asBareJid());
Data form = new Data(); final var form = new Data();
form.setFormType("http://jabber.org/protocol/muc#request"); form.setFormType("http://jabber.org/protocol/muc#request");
form.put("muc#role", "participant"); form.put("muc#role", "participant");
form.submit(); form.submit();
@ -218,9 +218,9 @@ public class MessageGenerator extends AbstractGenerator {
return packet; return packet;
} }
public MessagePacket directInvite(final Conversation conversation, final Jid contact) { public im.conversations.android.xmpp.model.stanza.Message directInvite(final Conversation conversation, final Jid contact) {
MessagePacket packet = new MessagePacket(); im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message();
packet.setType(MessagePacket.TYPE_NORMAL); packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.NORMAL);
packet.setTo(contact); packet.setTo(contact);
packet.setFrom(conversation.getAccount().getJid()); packet.setFrom(conversation.getAccount().getJid());
Element x = packet.addChild("x", "jabber:x:conference"); Element x = packet.addChild("x", "jabber:x:conference");
@ -236,8 +236,8 @@ public class MessageGenerator extends AbstractGenerator {
return packet; return packet;
} }
public MessagePacket invite(final Conversation conversation, final Jid contact) { public im.conversations.android.xmpp.model.stanza.Message invite(final Conversation conversation, final Jid contact) {
final MessagePacket packet = new MessagePacket(); final var packet = new im.conversations.android.xmpp.model.stanza.Message();
packet.setTo(conversation.getJid().asBareJid()); packet.setTo(conversation.getJid().asBareJid());
packet.setFrom(conversation.getAccount().getJid()); packet.setFrom(conversation.getAccount().getJid());
Element x = new Element("x"); Element x = new Element("x");
@ -249,8 +249,9 @@ public class MessageGenerator extends AbstractGenerator {
return packet; return packet;
} }
public MessagePacket received(Account account, final Jid from, final String id, ArrayList<String> namespaces, int type) { public im.conversations.android.xmpp.model.stanza.Message received(Account account, final Jid from, final String id, ArrayList<String> namespaces, im.conversations.android.xmpp.model.stanza.Message.Type type) {
final MessagePacket receivedPacket = new MessagePacket(); final var receivedPacket =
new im.conversations.android.xmpp.model.stanza.Message();
receivedPacket.setType(type); receivedPacket.setType(type);
receivedPacket.setTo(from); receivedPacket.setTo(from);
receivedPacket.setFrom(account.getJid()); receivedPacket.setFrom(account.getJid());
@ -261,8 +262,8 @@ public class MessageGenerator extends AbstractGenerator {
return receivedPacket; return receivedPacket;
} }
public MessagePacket received(Account account, Jid to, String id) { public im.conversations.android.xmpp.model.stanza.Message received(Account account, Jid to, String id) {
MessagePacket packet = new MessagePacket(); im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message();
packet.setFrom(account.getJid()); packet.setFrom(account.getJid());
packet.setTo(to); packet.setTo(to);
packet.addChild("received", "urn:xmpp:receipts").setAttribute("id", id); packet.addChild("received", "urn:xmpp:receipts").setAttribute("id", id);
@ -270,10 +271,10 @@ public class MessageGenerator extends AbstractGenerator {
return packet; return packet;
} }
public MessagePacket sessionFinish( public im.conversations.android.xmpp.model.stanza.Message sessionFinish(
final Jid with, final String sessionId, final Reason reason) { final Jid with, final String sessionId, final Reason reason) {
final MessagePacket packet = new MessagePacket(); final im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message();
packet.setType(MessagePacket.TYPE_CHAT); packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.CHAT);
packet.setTo(with); packet.setTo(with);
final Element finish = packet.addChild("finish", Namespace.JINGLE_MESSAGE); final Element finish = packet.addChild("finish", Namespace.JINGLE_MESSAGE);
finish.setAttribute("id", sessionId); finish.setAttribute("id", sessionId);
@ -283,9 +284,9 @@ public class MessageGenerator extends AbstractGenerator {
return packet; return packet;
} }
public MessagePacket sessionProposal(final JingleConnectionManager.RtpSessionProposal proposal) { public im.conversations.android.xmpp.model.stanza.Message sessionProposal(final JingleConnectionManager.RtpSessionProposal proposal) {
final MessagePacket packet = new MessagePacket(); final im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message();
packet.setType(MessagePacket.TYPE_CHAT); //we want to carbon copy those packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.CHAT); //we want to carbon copy those
packet.setTo(proposal.with); packet.setTo(proposal.with);
packet.setId(JingleRtpConnection.JINGLE_MESSAGE_PROPOSE_ID_PREFIX + proposal.sessionId); packet.setId(JingleRtpConnection.JINGLE_MESSAGE_PROPOSE_ID_PREFIX + proposal.sessionId);
final Element propose = packet.addChild("propose", Namespace.JINGLE_MESSAGE); final Element propose = packet.addChild("propose", Namespace.JINGLE_MESSAGE);
@ -298,9 +299,9 @@ public class MessageGenerator extends AbstractGenerator {
return packet; return packet;
} }
public MessagePacket sessionRetract(final JingleConnectionManager.RtpSessionProposal proposal) { public im.conversations.android.xmpp.model.stanza.Message sessionRetract(final JingleConnectionManager.RtpSessionProposal proposal) {
final MessagePacket packet = new MessagePacket(); final im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message();
packet.setType(MessagePacket.TYPE_CHAT); //we want to carbon copy those packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.CHAT); //we want to carbon copy those
packet.setTo(proposal.with); packet.setTo(proposal.with);
final Element propose = packet.addChild("retract", Namespace.JINGLE_MESSAGE); final Element propose = packet.addChild("retract", Namespace.JINGLE_MESSAGE);
propose.setAttribute("id", proposal.sessionId); propose.setAttribute("id", proposal.sessionId);
@ -309,9 +310,9 @@ public class MessageGenerator extends AbstractGenerator {
return packet; return packet;
} }
public MessagePacket sessionReject(final Jid with, final String sessionId) { public im.conversations.android.xmpp.model.stanza.Message sessionReject(final Jid with, final String sessionId) {
final MessagePacket packet = new MessagePacket(); final im.conversations.android.xmpp.model.stanza.Message packet = new im.conversations.android.xmpp.model.stanza.Message();
packet.setType(MessagePacket.TYPE_CHAT); //we want to carbon copy those packet.setType(im.conversations.android.xmpp.model.stanza.Message.Type.CHAT); //we want to carbon copy those
packet.setTo(with); packet.setTo(with);
final Element propose = packet.addChild("reject", Namespace.JINGLE_MESSAGE); final Element propose = packet.addChild("reject", Namespace.JINGLE_MESSAGE);
propose.setAttribute("id", sessionId); propose.setAttribute("id", sessionId);

View file

@ -9,7 +9,6 @@ import eu.siacs.conversations.entities.Presence;
import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xml.Namespace;
import eu.siacs.conversations.xmpp.stanzas.PresencePacket;
public class PresenceGenerator extends AbstractGenerator { public class PresenceGenerator extends AbstractGenerator {
@ -17,20 +16,20 @@ public class PresenceGenerator extends AbstractGenerator {
super(service); super(service);
} }
private PresencePacket subscription(String type, Contact contact) { private im.conversations.android.xmpp.model.stanza.Presence subscription(String type, Contact contact) {
PresencePacket packet = new PresencePacket(); im.conversations.android.xmpp.model.stanza.Presence packet = new im.conversations.android.xmpp.model.stanza.Presence();
packet.setAttribute("type", type); packet.setAttribute("type", type);
packet.setTo(contact.getJid()); packet.setTo(contact.getJid());
packet.setFrom(contact.getAccount().getJid().asBareJid()); packet.setFrom(contact.getAccount().getJid().asBareJid());
return packet; return packet;
} }
public PresencePacket requestPresenceUpdatesFrom(final Contact contact) { public im.conversations.android.xmpp.model.stanza.Presence requestPresenceUpdatesFrom(final Contact contact) {
return requestPresenceUpdatesFrom(contact, null); return requestPresenceUpdatesFrom(contact, null);
} }
public PresencePacket requestPresenceUpdatesFrom(final Contact contact, final String preAuth) { public im.conversations.android.xmpp.model.stanza.Presence requestPresenceUpdatesFrom(final Contact contact, final String preAuth) {
PresencePacket packet = subscription("subscribe", contact); im.conversations.android.xmpp.model.stanza.Presence packet = subscription("subscribe", contact);
String displayName = contact.getAccount().getDisplayName(); String displayName = contact.getAccount().getDisplayName();
if (!TextUtils.isEmpty(displayName)) { if (!TextUtils.isEmpty(displayName)) {
packet.addChild("nick", Namespace.NICK).setContent(displayName); packet.addChild("nick", Namespace.NICK).setContent(displayName);
@ -41,24 +40,24 @@ public class PresenceGenerator extends AbstractGenerator {
return packet; return packet;
} }
public PresencePacket stopPresenceUpdatesFrom(Contact contact) { public im.conversations.android.xmpp.model.stanza.Presence stopPresenceUpdatesFrom(Contact contact) {
return subscription("unsubscribe", contact); return subscription("unsubscribe", contact);
} }
public PresencePacket stopPresenceUpdatesTo(Contact contact) { public im.conversations.android.xmpp.model.stanza.Presence stopPresenceUpdatesTo(Contact contact) {
return subscription("unsubscribed", contact); return subscription("unsubscribed", contact);
} }
public PresencePacket sendPresenceUpdatesTo(Contact contact) { public im.conversations.android.xmpp.model.stanza.Presence sendPresenceUpdatesTo(Contact contact) {
return subscription("subscribed", contact); return subscription("subscribed", contact);
} }
public PresencePacket selfPresence(Account account, Presence.Status status) { public im.conversations.android.xmpp.model.stanza.Presence selfPresence(Account account, Presence.Status status) {
return selfPresence(account, status, true, null); return selfPresence(account, status, true, null);
} }
public PresencePacket selfPresence(final Account account, final Presence.Status status, final boolean personal, final String nickname) { public im.conversations.android.xmpp.model.stanza.Presence selfPresence(final Account account, final Presence.Status status, final boolean personal, final String nickname) {
final PresencePacket packet = new PresencePacket(); final im.conversations.android.xmpp.model.stanza.Presence packet = new im.conversations.android.xmpp.model.stanza.Presence();
if (personal) { if (personal) {
final String sig = account.getPgpSignature(); final String sig = account.getPgpSignature();
final String message = account.getPresenceStatusMessage(); final String message = account.getPresenceStatusMessage();
@ -87,16 +86,16 @@ public class PresenceGenerator extends AbstractGenerator {
return packet; return packet;
} }
public PresencePacket leave(final MucOptions mucOptions) { public im.conversations.android.xmpp.model.stanza.Presence leave(final MucOptions mucOptions) {
PresencePacket presencePacket = new PresencePacket(); im.conversations.android.xmpp.model.stanza.Presence presence = new im.conversations.android.xmpp.model.stanza.Presence();
presencePacket.setTo(mucOptions.getSelf().getFullJid()); presence.setTo(mucOptions.getSelf().getFullJid());
presencePacket.setFrom(mucOptions.getAccount().getJid()); presence.setFrom(mucOptions.getAccount().getJid());
presencePacket.setAttribute("type", "unavailable"); presence.setAttribute("type", "unavailable");
return presencePacket; return presence;
} }
public PresencePacket sendOfflinePresence(Account account) { public im.conversations.android.xmpp.model.stanza.Presence sendOfflinePresence(Account account) {
PresencePacket packet = new PresencePacket(); im.conversations.android.xmpp.model.stanza.Presence packet = new im.conversations.android.xmpp.model.stanza.Presence();
packet.setFrom(account.getJid()); packet.setFrom(account.getJid());
packet.setAttribute("type", "unavailable"); packet.setAttribute("type", "unavailable");
return packet; return packet;

View file

@ -2,11 +2,27 @@ package eu.siacs.conversations.http;
import static eu.siacs.conversations.utils.Random.SECURE_RANDOM; import static eu.siacs.conversations.utils.Random.SECURE_RANDOM;
import android.content.Context;
import android.os.Build; import android.os.Build;
import android.util.Log; import android.util.Log;
import androidx.core.util.Consumer; import androidx.core.util.Consumer;
import eu.siacs.conversations.BuildConfig;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.crypto.TrustManagers;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.DownloadableFile;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.services.AbstractConnectionManager;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.utils.TLSSocketFactory;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.ResponseBody;
import org.apache.http.conn.ssl.StrictHostnameVerifier; import org.apache.http.conn.ssl.StrictHostnameVerifier;
import java.io.IOException; import java.io.IOException;
@ -16,7 +32,9 @@ import java.net.InetSocketAddress;
import java.net.Proxy; import java.net.Proxy;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.security.KeyManagementException; import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.Executor; import java.util.concurrent.Executor;
@ -26,19 +44,6 @@ import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.X509TrustManager; import javax.net.ssl.X509TrustManager;
import eu.siacs.conversations.BuildConfig;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.DownloadableFile;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.services.AbstractConnectionManager;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.utils.TLSSocketFactory;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.ResponseBody;
public class HttpConnectionManager extends AbstractConnectionManager { public class HttpConnectionManager extends AbstractConnectionManager {
private final List<HttpDownloadConnection> downloadConnections = new ArrayList<>(); private final List<HttpDownloadConnection> downloadConnections = new ArrayList<>();
@ -46,7 +51,7 @@ public class HttpConnectionManager extends AbstractConnectionManager {
public static final Executor EXECUTOR = Executors.newFixedThreadPool(4); public static final Executor EXECUTOR = Executors.newFixedThreadPool(4);
public static final OkHttpClient OK_HTTP_CLIENT; private static final OkHttpClient OK_HTTP_CLIENT;
static { static {
OK_HTTP_CLIENT = new OkHttpClient.Builder() OK_HTTP_CLIENT = new OkHttpClient.Builder()
@ -209,4 +214,27 @@ public class HttpConnectionManager extends AbstractConnectionManager {
return filename; return filename;
} }
public static OkHttpClient okHttpClient(final Context context) {
final OkHttpClient.Builder builder = HttpConnectionManager.OK_HTTP_CLIENT.newBuilder();
try {
final X509TrustManager trustManager;
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N) {
trustManager = TrustManagers.defaultWithBundledLetsEncrypt(context);
} else {
trustManager = TrustManagers.createDefaultTrustManager();
}
final SSLSocketFactory socketFactory =
new TLSSocketFactory(new X509TrustManager[] {trustManager}, SECURE_RANDOM);
builder.sslSocketFactory(socketFactory, trustManager);
} catch (final IOException
| KeyManagementException
| NoSuchAlgorithmException
| KeyStoreException
| CertificateException e) {
Log.d(Config.LOGTAG, "not reconfiguring service to work with bundled LetsEncrypt");
throw new RuntimeException(e);
}
return builder.build();
}
} }

View file

@ -43,7 +43,7 @@ import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xml.Namespace;
import eu.siacs.conversations.xmpp.IqResponseException; import eu.siacs.conversations.xmpp.IqResponseException;
import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.Jid;
import eu.siacs.conversations.xmpp.stanzas.IqPacket; import im.conversations.android.xmpp.model.stanza.Iq;
import okhttp3.Headers; import okhttp3.Headers;
import okhttp3.HttpUrl; import okhttp3.HttpUrl;
@ -67,9 +67,9 @@ public class SlotRequester {
private ListenableFuture<Slot> requestHttpUploadLegacy(Account account, Jid host, DownloadableFile file, String mime) { private ListenableFuture<Slot> requestHttpUploadLegacy(Account account, Jid host, DownloadableFile file, String mime) {
final SettableFuture<Slot> future = SettableFuture.create(); final SettableFuture<Slot> future = SettableFuture.create();
final IqPacket request = service.getIqGenerator().requestHttpUploadLegacySlot(host, file, mime); final Iq request = service.getIqGenerator().requestHttpUploadLegacySlot(host, file, mime);
service.sendIqPacket(account, request, (a, packet) -> { service.sendIqPacket(account, request, (packet) -> {
if (packet.getType() == IqPacket.TYPE.RESULT) { if (packet.getType() == Iq.Type.RESULT) {
final Element slotElement = packet.findChild("slot", Namespace.HTTP_UPLOAD_LEGACY); final Element slotElement = packet.findChild("slot", Namespace.HTTP_UPLOAD_LEGACY);
if (slotElement != null) { if (slotElement != null) {
try { try {
@ -97,9 +97,9 @@ public class SlotRequester {
private ListenableFuture<Slot> requestHttpUpload(Account account, Jid host, DownloadableFile file, String fname, String mime) { private ListenableFuture<Slot> requestHttpUpload(Account account, Jid host, DownloadableFile file, String fname, String mime) {
final SettableFuture<Slot> future = SettableFuture.create(); final SettableFuture<Slot> future = SettableFuture.create();
final IqPacket request = service.getIqGenerator().requestHttpUploadSlot(host, file, fname, mime); final Iq request = service.getIqGenerator().requestHttpUploadSlot(host, file, fname, mime);
service.sendIqPacket(account, request, (a, packet) -> { service.sendIqPacket(account, request, (packet) -> {
if (packet.getType() == IqPacket.TYPE.RESULT) { if (packet.getType() == Iq.Type.RESULT) {
final Element slotElement = packet.findChild("slot", Namespace.HTTP_UPLOAD); final Element slotElement = packet.findChild("slot", Namespace.HTTP_UPLOAD);
if (slotElement != null) { if (slotElement != null) {
try { try {

View file

@ -18,14 +18,16 @@ import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xmpp.InvalidJid; import eu.siacs.conversations.xmpp.InvalidJid;
import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.Jid;
import eu.siacs.conversations.xmpp.stanzas.AbstractStanza; import im.conversations.android.xmpp.model.stanza.Stanza;
public abstract class AbstractParser { public abstract class AbstractParser {
protected XmppConnectionService mXmppConnectionService; protected final XmppConnectionService mXmppConnectionService;
protected final Account account;
protected AbstractParser(XmppConnectionService service) { protected AbstractParser(final XmppConnectionService service, final Account account) {
this.mXmppConnectionService = service; this.mXmppConnectionService = service;
this.account = account;
} }
public static Long parseTimestamp(Element element, Long d) { public static Long parseTimestamp(Element element, Long d) {
@ -36,8 +38,8 @@ public abstract class AbstractParser {
long min = Long.MAX_VALUE; long min = Long.MAX_VALUE;
boolean returnDefault = true; boolean returnDefault = true;
final Jid to; final Jid to;
if (ignoreCsiAndSm && element instanceof AbstractStanza) { if (ignoreCsiAndSm && element instanceof Stanza stanza) {
to = ((AbstractStanza) element).getTo(); to = stanza.getTo();
} else { } else {
to = null; to = null;
} }
@ -125,7 +127,7 @@ public abstract class AbstractParser {
contact.setLastResource(from.isBareJid() ? "" : from.getResource()); contact.setLastResource(from.isBareJid() ? "" : from.getResource());
} }
protected String avatarData(Element items) { protected static String avatarData(Element items) {
Element item = items.findChild("item"); Element item = items.findChild("item");
if (item == null) { if (item == null) {
return null; return null;

View file

@ -26,6 +26,7 @@ import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.function.Consumer;
import eu.siacs.conversations.Config; import eu.siacs.conversations.Config;
import eu.siacs.conversations.crypto.axolotl.AxolotlService; import eu.siacs.conversations.crypto.axolotl.AxolotlService;
@ -38,18 +39,17 @@ import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xml.Namespace;
import eu.siacs.conversations.xmpp.InvalidJid; import eu.siacs.conversations.xmpp.InvalidJid;
import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.Jid;
import eu.siacs.conversations.xmpp.OnIqPacketReceived;
import eu.siacs.conversations.xmpp.OnUpdateBlocklist; import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
import eu.siacs.conversations.xmpp.forms.Data; import eu.siacs.conversations.xmpp.forms.Data;
import eu.siacs.conversations.xmpp.stanzas.IqPacket; import im.conversations.android.xmpp.model.stanza.Iq;
public class IqParser extends AbstractParser implements OnIqPacketReceived { public class IqParser extends AbstractParser implements Consumer<Iq> {
public IqParser(final XmppConnectionService service) { public IqParser(final XmppConnectionService service, final Account account) {
super(service); super(service, account);
} }
public static List<Jid> items(IqPacket packet) { public static List<Jid> items(final Iq packet) {
ArrayList<Jid> items = new ArrayList<>(); ArrayList<Jid> items = new ArrayList<>();
final Element query = packet.findChild("query", Namespace.DISCO_ITEMS); final Element query = packet.findChild("query", Namespace.DISCO_ITEMS);
if (query == null) { if (query == null) {
@ -66,7 +66,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
return items; return items;
} }
public static Room parseRoom(IqPacket packet) { public static Room parseRoom(Iq packet) {
final Element query = packet.findChild("query", Namespace.DISCO_INFO); final Element query = packet.findChild("query", Namespace.DISCO_INFO);
if (query == null) { if (query == null) {
return null; return null;
@ -144,7 +144,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
mXmppConnectionService.syncRoster(account); mXmppConnectionService.syncRoster(account);
} }
public String avatarData(final IqPacket packet) { public static String avatarData(final Iq packet) {
final Element pubsub = packet.findChild("pubsub", Namespace.PUBSUB); final Element pubsub = packet.findChild("pubsub", Namespace.PUBSUB);
if (pubsub == null) { if (pubsub == null) {
return null; return null;
@ -153,10 +153,10 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
if (items == null) { if (items == null) {
return null; return null;
} }
return super.avatarData(items); return AbstractParser.avatarData(items);
} }
public Element getItem(final IqPacket packet) { public static Element getItem(final Iq packet) {
final Element pubsub = packet.findChild("pubsub", Namespace.PUBSUB); final Element pubsub = packet.findChild("pubsub", Namespace.PUBSUB);
if (pubsub == null) { if (pubsub == null) {
return null; return null;
@ -169,7 +169,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
} }
@NonNull @NonNull
public Set<Integer> deviceIds(final Element item) { public static Set<Integer> deviceIds(final Element item) {
Set<Integer> deviceIds = new HashSet<>(); Set<Integer> deviceIds = new HashSet<>();
if (item != null) { if (item != null) {
final Element list = item.findChild("list"); final Element list = item.findChild("list");
@ -190,7 +190,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
return deviceIds; return deviceIds;
} }
private Integer signedPreKeyId(final Element bundle) { private static Integer signedPreKeyId(final Element bundle) {
final Element signedPreKeyPublic = bundle.findChild("signedPreKeyPublic"); final Element signedPreKeyPublic = bundle.findChild("signedPreKeyPublic");
if (signedPreKeyPublic == null) { if (signedPreKeyPublic == null) {
return null; return null;
@ -202,7 +202,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
} }
} }
private ECPublicKey signedPreKeyPublic(final Element bundle) { private static ECPublicKey signedPreKeyPublic(final Element bundle) {
ECPublicKey publicKey = null; ECPublicKey publicKey = null;
final String signedPreKeyPublic = bundle.findChildContent("signedPreKeyPublic"); final String signedPreKeyPublic = bundle.findChildContent("signedPreKeyPublic");
if (signedPreKeyPublic == null) { if (signedPreKeyPublic == null) {
@ -216,7 +216,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
return publicKey; return publicKey;
} }
private byte[] signedPreKeySignature(final Element bundle) { private static byte[] signedPreKeySignature(final Element bundle) {
final String signedPreKeySignature = bundle.findChildContent("signedPreKeySignature"); final String signedPreKeySignature = bundle.findChildContent("signedPreKeySignature");
if (signedPreKeySignature == null) { if (signedPreKeySignature == null) {
return null; return null;
@ -229,7 +229,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
} }
} }
private IdentityKey identityKey(final Element bundle) { private static IdentityKey identityKey(final Element bundle) {
final String identityKey = bundle.findChildContent("identityKey"); final String identityKey = bundle.findChildContent("identityKey");
if (identityKey == null) { if (identityKey == null) {
return null; return null;
@ -242,7 +242,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
} }
} }
public Map<Integer, ECPublicKey> preKeyPublics(final IqPacket packet) { public static Map<Integer, ECPublicKey> preKeyPublics(final Iq packet) {
Map<Integer, ECPublicKey> preKeyRecords = new HashMap<>(); Map<Integer, ECPublicKey> preKeyRecords = new HashMap<>();
Element item = getItem(packet); Element item = getItem(packet);
if (item == null) { if (item == null) {
@ -285,7 +285,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
return BaseEncoding.base64().decode(CharMatcher.whitespace().removeFrom(input)); return BaseEncoding.base64().decode(CharMatcher.whitespace().removeFrom(input));
} }
public Pair<X509Certificate[], byte[]> verification(final IqPacket packet) { public static Pair<X509Certificate[], byte[]> verification(final Iq packet) {
Element item = getItem(packet); Element item = getItem(packet);
Element verification = item != null ? item.findChild("verification", AxolotlService.PEP_PREFIX) : null; Element verification = item != null ? item.findChild("verification", AxolotlService.PEP_PREFIX) : null;
Element chain = verification != null ? verification.findChild("chain") : null; Element chain = verification != null ? verification.findChild("chain") : null;
@ -313,7 +313,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
} }
} }
public PreKeyBundle bundle(final IqPacket bundle) { public static PreKeyBundle bundle(final Iq bundle) {
final Element bundleItem = getItem(bundle); final Element bundleItem = getItem(bundle);
if (bundleItem == null) { if (bundleItem == null) {
return null; return null;
@ -337,7 +337,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
signedPreKeyId, signedPreKeyPublic, signedPreKeySignature, identityKey); signedPreKeyId, signedPreKeyPublic, signedPreKeySignature, identityKey);
} }
public List<PreKeyBundle> preKeys(final IqPacket preKeys) { public static List<PreKeyBundle> preKeys(final Iq preKeys) {
List<PreKeyBundle> bundles = new ArrayList<>(); List<PreKeyBundle> bundles = new ArrayList<>();
Map<Integer, ECPublicKey> preKeyPublics = preKeyPublics(preKeys); Map<Integer, ECPublicKey> preKeyPublics = preKeyPublics(preKeys);
if (preKeyPublics != null) { if (preKeyPublics != null) {
@ -352,15 +352,15 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
} }
@Override @Override
public void onIqPacketReceived(final Account account, final IqPacket packet) { public void accept(final Iq packet) {
final boolean isGet = packet.getType() == IqPacket.TYPE.GET; final boolean isGet = packet.getType() == Iq.Type.GET;
if (packet.getType() == IqPacket.TYPE.ERROR || packet.getType() == IqPacket.TYPE.TIMEOUT) { if (packet.getType() == Iq.Type.ERROR || packet.getType() == Iq.Type.TIMEOUT) {
return; return;
} }
if (packet.hasChild("query", Namespace.ROSTER) && packet.fromServer(account)) { if (packet.hasChild("query", Namespace.ROSTER) && packet.fromServer(account)) {
final Element query = packet.findChild("query"); final Element query = packet.findChild("query");
// If this is in response to a query for the whole roster: // If this is in response to a query for the whole roster:
if (packet.getType() == IqPacket.TYPE.RESULT) { if (packet.getType() == Iq.Type.RESULT) {
account.getRoster().markAllAsNotInRoster(); account.getRoster().markAllAsNotInRoster();
} }
this.rosterItems(account, query); this.rosterItems(account, query);
@ -374,7 +374,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
(block != null ? block.getChildren() : null); (block != null ? block.getChildren() : null);
// If this is a response to a blocklist query, clear the block list and replace with the new one. // If this is a response to a blocklist query, clear the block list and replace with the new one.
// Otherwise, just update the existing blocklist. // Otherwise, just update the existing blocklist.
if (packet.getType() == IqPacket.TYPE.RESULT) { if (packet.getType() == Iq.Type.RESULT) {
account.clearBlocklist(); account.clearBlocklist();
account.getXmppConnection().getFeatures().setBlockListRequested(true); account.getXmppConnection().getFeatures().setBlockListRequested(true);
} }
@ -390,7 +390,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
} }
} }
account.getBlocklist().addAll(jids); account.getBlocklist().addAll(jids);
if (packet.getType() == IqPacket.TYPE.SET) { if (packet.getType() == Iq.Type.SET) {
boolean removed = false; boolean removed = false;
for (Jid jid : jids) { for (Jid jid : jids) {
removed |= mXmppConnectionService.removeBlockedConversations(account, jid); removed |= mXmppConnectionService.removeBlockedConversations(account, jid);
@ -402,15 +402,15 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
} }
// Update the UI // Update the UI
mXmppConnectionService.updateBlocklistUi(OnUpdateBlocklist.Status.BLOCKED); mXmppConnectionService.updateBlocklistUi(OnUpdateBlocklist.Status.BLOCKED);
if (packet.getType() == IqPacket.TYPE.SET) { if (packet.getType() == Iq.Type.SET) {
final IqPacket response = packet.generateResponse(IqPacket.TYPE.RESULT); final Iq response = packet.generateResponse(Iq.Type.RESULT);
mXmppConnectionService.sendIqPacket(account, response, null); mXmppConnectionService.sendIqPacket(account, response, null);
} }
} else if (packet.hasChild("unblock", Namespace.BLOCKING) && } else if (packet.hasChild("unblock", Namespace.BLOCKING) &&
packet.fromServer(account) && packet.getType() == IqPacket.TYPE.SET) { packet.fromServer(account) && packet.getType() == Iq.Type.SET) {
Log.d(Config.LOGTAG, "Received unblock update from server"); Log.d(Config.LOGTAG, "Received unblock update from server");
final Collection<Element> items = packet.findChild("unblock", Namespace.BLOCKING).getChildren(); final Collection<Element> items = packet.findChild("unblock", Namespace.BLOCKING).getChildren();
if (items.size() == 0) { if (items.isEmpty()) {
// No children to unblock == unblock all // No children to unblock == unblock all
account.getBlocklist().clear(); account.getBlocklist().clear();
} else { } else {
@ -426,7 +426,7 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
account.getBlocklist().removeAll(jids); account.getBlocklist().removeAll(jids);
} }
mXmppConnectionService.updateBlocklistUi(OnUpdateBlocklist.Status.UNBLOCKED); mXmppConnectionService.updateBlocklistUi(OnUpdateBlocklist.Status.UNBLOCKED);
final IqPacket response = packet.generateResponse(IqPacket.TYPE.RESULT); final Iq response = packet.generateResponse(Iq.Type.RESULT);
mXmppConnectionService.sendIqPacket(account, response, null); mXmppConnectionService.sendIqPacket(account, response, null);
} else if (packet.hasChild("open", "http://jabber.org/protocol/ibb") } else if (packet.hasChild("open", "http://jabber.org/protocol/ibb")
|| packet.hasChild("data", "http://jabber.org/protocol/ibb") || packet.hasChild("data", "http://jabber.org/protocol/ibb")
@ -434,18 +434,18 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
mXmppConnectionService.getJingleConnectionManager() mXmppConnectionService.getJingleConnectionManager()
.deliverIbbPacket(account, packet); .deliverIbbPacket(account, packet);
} else if (packet.hasChild("query", "http://jabber.org/protocol/disco#info")) { } else if (packet.hasChild("query", "http://jabber.org/protocol/disco#info")) {
final IqPacket response = mXmppConnectionService.getIqGenerator().discoResponse(account, packet); final Iq response = mXmppConnectionService.getIqGenerator().discoResponse(account, packet);
mXmppConnectionService.sendIqPacket(account, response, null); mXmppConnectionService.sendIqPacket(account, response, null);
} else if (packet.hasChild("query", "jabber:iq:version") && isGet) { } else if (packet.hasChild("query", "jabber:iq:version") && isGet) {
final IqPacket response = mXmppConnectionService.getIqGenerator().versionResponse(packet); final Iq response = mXmppConnectionService.getIqGenerator().versionResponse(packet);
mXmppConnectionService.sendIqPacket(account, response, null); mXmppConnectionService.sendIqPacket(account, response, null);
} else if (packet.hasChild("ping", "urn:xmpp:ping") && isGet) { } else if (packet.hasChild("ping", "urn:xmpp:ping") && isGet) {
final IqPacket response = packet.generateResponse(IqPacket.TYPE.RESULT); final Iq response = packet.generateResponse(Iq.Type.RESULT);
mXmppConnectionService.sendIqPacket(account, response, null); mXmppConnectionService.sendIqPacket(account, response, null);
} else if (packet.hasChild("time", "urn:xmpp:time") && isGet) { } else if (packet.hasChild("time", "urn:xmpp:time") && isGet) {
final IqPacket response; final Iq response;
if (mXmppConnectionService.useTorToConnect() || account.isOnion()) { if (mXmppConnectionService.useTorToConnect() || account.isOnion()) {
response = packet.generateResponse(IqPacket.TYPE.ERROR); response = packet.generateResponse(Iq.Type.ERROR);
final Element error = response.addChild("error"); final Element error = response.addChild("error");
error.setAttribute("type", "cancel"); error.setAttribute("type", "cancel");
error.addChild("not-allowed", "urn:ietf:params:xml:ns:xmpp-stanzas"); error.addChild("not-allowed", "urn:ietf:params:xml:ns:xmpp-stanzas");
@ -453,18 +453,18 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
response = mXmppConnectionService.getIqGenerator().entityTimeResponse(packet); response = mXmppConnectionService.getIqGenerator().entityTimeResponse(packet);
} }
mXmppConnectionService.sendIqPacket(account, response, null); mXmppConnectionService.sendIqPacket(account, response, null);
} else if (packet.hasChild("push", Namespace.UNIFIED_PUSH) && packet.getType() == IqPacket.TYPE.SET) { } else if (packet.hasChild("push", Namespace.UNIFIED_PUSH) && packet.getType() == Iq.Type.SET) {
final Jid transport = packet.getFrom(); final Jid transport = packet.getFrom();
final Element push = packet.findChild("push", Namespace.UNIFIED_PUSH); final Element push = packet.findChild("push", Namespace.UNIFIED_PUSH);
final boolean success = final boolean success =
push != null push != null
&& mXmppConnectionService.processUnifiedPushMessage( && mXmppConnectionService.processUnifiedPushMessage(
account, transport, push); account, transport, push);
final IqPacket response; final Iq response;
if (success) { if (success) {
response = packet.generateResponse(IqPacket.TYPE.RESULT); response = packet.generateResponse(Iq.Type.RESULT);
} else { } else {
response = packet.generateResponse(IqPacket.TYPE.ERROR); response = packet.generateResponse(Iq.Type.ERROR);
final Element error = response.addChild("error"); final Element error = response.addChild("error");
error.setAttribute("type", "cancel"); error.setAttribute("type", "cancel");
error.setAttribute("code", "404"); error.setAttribute("code", "404");
@ -476,8 +476,8 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
final Conversation conversation = mXmppConnectionService.find(account, packet.getFrom()); final Conversation conversation = mXmppConnectionService.find(account, packet.getFrom());
if (packet.hasChild("data", "urn:xmpp:bob") && isGet && (conversation == null ? contact != null && contact.canInferPresence() : conversation.canInferPresence())) { if (packet.hasChild("data", "urn:xmpp:bob") && isGet && (conversation == null ? contact != null && contact.canInferPresence() : conversation.canInferPresence())) {
mXmppConnectionService.sendIqPacket(account, mXmppConnectionService.getIqGenerator().bobResponse(packet), null); mXmppConnectionService.sendIqPacket(account, mXmppConnectionService.getIqGenerator().bobResponse(packet), null);
} else if (packet.getType() == IqPacket.TYPE.GET || packet.getType() == IqPacket.TYPE.SET) { } else if (packet.getType() == Iq.Type.GET || packet.getType() == Iq.Type.SET) {
final IqPacket response = packet.generateResponse(IqPacket.TYPE.ERROR); final var response = packet.generateResponse(Iq.Type.ERROR);
final Element error = response.addChild("error"); final Element error = response.addChild("error");
error.setAttribute("type", "cancel"); error.setAttribute("type", "cancel");
error.addChild("feature-not-implemented", "urn:ietf:params:xml:ns:xmpp-stanzas"); error.addChild("feature-not-implemented", "urn:ietf:params:xml:ns:xmpp-stanzas");

View file

@ -22,6 +22,7 @@ import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.function.Consumer;
import io.ipfs.cid.Cid; import io.ipfs.cid.Cid;
@ -60,17 +61,20 @@ import eu.siacs.conversations.xmpp.forms.Data;
import eu.siacs.conversations.xmpp.jingle.JingleConnectionManager; import eu.siacs.conversations.xmpp.jingle.JingleConnectionManager;
import eu.siacs.conversations.xmpp.jingle.JingleRtpConnection; import eu.siacs.conversations.xmpp.jingle.JingleRtpConnection;
import eu.siacs.conversations.xmpp.pep.Avatar; import eu.siacs.conversations.xmpp.pep.Avatar;
import eu.siacs.conversations.xmpp.stanzas.MessagePacket; import im.conversations.android.xmpp.model.Extension;
import im.conversations.android.xmpp.model.carbons.Received;
import im.conversations.android.xmpp.model.carbons.Sent;
import im.conversations.android.xmpp.model.forward.Forwarded;
public class MessageParser extends AbstractParser implements OnMessagePacketReceived { public class MessageParser extends AbstractParser implements Consumer<im.conversations.android.xmpp.model.stanza.Message> {
private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm:ss", Locale.ENGLISH); private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm:ss", Locale.ENGLISH);
private static final List<String> JINGLE_MESSAGE_ELEMENT_NAMES = private static final List<String> JINGLE_MESSAGE_ELEMENT_NAMES =
Arrays.asList("accept", "propose", "proceed", "reject", "retract", "ringing", "finish"); Arrays.asList("accept", "propose", "proceed", "reject", "retract", "ringing", "finish");
public MessageParser(XmppConnectionService service) { public MessageParser(final XmppConnectionService service, final Account account) {
super(service); super(service, account);
} }
private static String extractStanzaId(Element packet, boolean isTypeGroupChat, Conversation conversation) { private static String extractStanzaId(Element packet, boolean isTypeGroupChat, Conversation conversation) {
@ -109,7 +113,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
return result != null ? result : fallback; return result != null ? result : fallback;
} }
private boolean extractChatState(Conversation c, final boolean isTypeGroupChat, final MessagePacket packet) { private boolean extractChatState(Conversation c, final boolean isTypeGroupChat, final im.conversations.android.xmpp.model.stanza.Message packet) {
ChatState state = ChatState.parse(packet); ChatState state = ChatState.parse(packet);
if (state != null && c != null) { if (state != null && c != null) {
final Account account = c.getAccount(); final Account account = c.getAccount();
@ -251,7 +255,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
} }
} else if (AxolotlService.PEP_DEVICE_LIST.equals(node)) { } else if (AxolotlService.PEP_DEVICE_LIST.equals(node)) {
Element item = items.findChild("item"); Element item = items.findChild("item");
Set<Integer> deviceIds = mXmppConnectionService.getIqParser().deviceIds(item); final Set<Integer> deviceIds = IqParser.deviceIds(item);
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received PEP device list " + deviceIds + " update from " + from + ", processing... "); Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received PEP device list " + deviceIds + " update from " + from + ", processing... ");
final AxolotlService axolotlService = account.getAxolotlService(); final AxolotlService axolotlService = account.getAxolotlService();
axolotlService.registerDevices(from, deviceIds); axolotlService.registerDevices(from, deviceIds);
@ -358,10 +362,10 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
mXmppConnectionService.updateAccountUi(); mXmppConnectionService.updateAccountUi();
} }
private boolean handleErrorMessage(final Account account, final MessagePacket packet) { private boolean handleErrorMessage(final Account account, final im.conversations.android.xmpp.model.stanza.Message packet) {
if (packet.getType() == MessagePacket.TYPE_ERROR) { if (packet.getType() == im.conversations.android.xmpp.model.stanza.Message.Type.ERROR) {
if (packet.fromServer(account)) { if (packet.fromServer(account)) {
final Pair<MessagePacket, Long> forwarded = packet.getForwardedMessagePacket("received", Namespace.CARBONS); final var forwarded = getForwardedMessagePacket(packet,"received", Namespace.CARBONS);
if (forwarded != null) { if (forwarded != null) {
return handleErrorMessage(account, forwarded.first); return handleErrorMessage(account, forwarded.first);
} }
@ -404,11 +408,11 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
} }
@Override @Override
public void onMessagePacketReceived(Account account, MessagePacket original) { public void accept(final im.conversations.android.xmpp.model.stanza.Message original) {
if (handleErrorMessage(account, original)) { if (handleErrorMessage(account, original)) {
return; return;
} }
final MessagePacket packet; final im.conversations.android.xmpp.model.stanza.Message packet;
Long timestamp = null; Long timestamp = null;
boolean isCarbon = false; boolean isCarbon = false;
String serverMsgId = null; String serverMsgId = null;
@ -422,7 +426,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
final MessageArchiveService.Query query = queryId == null ? null : mXmppConnectionService.getMessageArchiveService().findQuery(queryId); final MessageArchiveService.Query query = queryId == null ? null : mXmppConnectionService.getMessageArchiveService().findQuery(queryId);
final boolean offlineMessagesRetrieved = account.getXmppConnection().isOfflineMessagesRetrieved(); final boolean offlineMessagesRetrieved = account.getXmppConnection().isOfflineMessagesRetrieved();
if (query != null && query.validFrom(original.getFrom())) { if (query != null && query.validFrom(original.getFrom())) {
final Pair<MessagePacket, Long> f = original.getForwardedMessagePacket("result", query.version.namespace); final var f = getForwardedMessagePacket(original,"result", query.version.namespace);
if (f == null) { if (f == null) {
return; return;
} }
@ -442,9 +446,9 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received mam result with invalid from (" + original.getFrom() + ") or queryId (" + queryId + ")"); Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received mam result with invalid from (" + original.getFrom() + ") or queryId (" + queryId + ")");
return; return;
} else if (original.fromServer(account)) { } else if (original.fromServer(account)) {
Pair<MessagePacket, Long> f; Pair<im.conversations.android.xmpp.model.stanza.Message, Long> f;
f = original.getForwardedMessagePacket("received", Namespace.CARBONS); f = getForwardedMessagePacket(original, Received.class);
f = f == null ? original.getForwardedMessagePacket("sent", Namespace.CARBONS) : f; f = f == null ? getForwardedMessagePacket(original, Sent.class) : f;
packet = f != null ? f.first : original; packet = f != null ? f.first : original;
if (handleErrorMessage(account, packet)) { if (handleErrorMessage(account, packet)) {
return; return;
@ -514,7 +518,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
return; return;
} }
boolean isTypeGroupChat = packet.getType() == MessagePacket.TYPE_GROUPCHAT; boolean isTypeGroupChat = packet.getType() == im.conversations.android.xmpp.model.stanza.Message.Type.GROUPCHAT;
if (query != null && !query.muc() && isTypeGroupChat) { if (query != null && !query.muc() && isTypeGroupChat) {
Log.e(Config.LOGTAG, account.getJid().asBareJid() + ": received groupchat (" + from + ") message on regular MAM request. skipping"); Log.e(Config.LOGTAG, account.getJid().asBareJid() + ": received groupchat (" + from + ") message on regular MAM request. skipping");
return; return;
@ -1300,6 +1304,34 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
} }
} }
private static Pair<im.conversations.android.xmpp.model.stanza.Message,Long> getForwardedMessagePacket(final im.conversations.android.xmpp.model.stanza.Message original, Class<? extends Extension> clazz) {
final var extension = original.getExtension(clazz);
final var forwarded = extension == null ? null : extension.getExtension(Forwarded.class);
if (forwarded == null) {
return null;
}
final Long timestamp = AbstractParser.parseTimestamp(forwarded, null);
final var forwardedMessage = forwarded.getMessage();
if (forwardedMessage == null) {
return null;
}
return new Pair<>(forwardedMessage,timestamp);
}
private static Pair<im.conversations.android.xmpp.model.stanza.Message,Long> getForwardedMessagePacket(final im.conversations.android.xmpp.model.stanza.Message original, final String name, final String namespace) {
final Element wrapper = original.findChild(name, namespace);
final var forwardedElement = wrapper == null ? null : wrapper.findChild("forwarded",Namespace.FORWARD);
if (forwardedElement instanceof Forwarded forwarded) {
final Long timestamp = AbstractParser.parseTimestamp(forwarded, null);
final var forwardedMessage = forwarded.getMessage();
if (forwardedMessage == null) {
return null;
}
return new Pair<>(forwardedMessage,timestamp);
}
return null;
}
private void dismissNotification(Account account, Jid counterpart, MessageArchiveService.Query query, final String id) { private void dismissNotification(Account account, Jid counterpart, MessageArchiveService.Query query, final String id) {
final Conversation conversation = mXmppConnectionService.find(account, counterpart.asBareJid()); final Conversation conversation = mXmppConnectionService.find(account, counterpart.asBareJid());
if (conversation != null && (query == null || query.isCatchup())) { if (conversation != null && (query == null || query.isCatchup())) {
@ -1312,7 +1344,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
} }
} }
private void processMessageReceipts(final Account account, final MessagePacket packet, final String remoteMsgId, MessageArchiveService.Query query) { private void processMessageReceipts(final Account account, final im.conversations.android.xmpp.model.stanza.Message packet, final String remoteMsgId, MessageArchiveService.Query query) {
final boolean markable = packet.hasChild("markable", "urn:xmpp:chat-markers:0"); final boolean markable = packet.hasChild("markable", "urn:xmpp:chat-markers:0");
final boolean request = packet.hasChild("request", "urn:xmpp:receipts"); final boolean request = packet.hasChild("request", "urn:xmpp:receipts");
if (query == null) { if (query == null) {
@ -1324,7 +1356,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
receiptsNamespaces.add("urn:xmpp:receipts"); receiptsNamespaces.add("urn:xmpp:receipts");
} }
if (receiptsNamespaces.size() > 0) { if (receiptsNamespaces.size() > 0) {
final MessagePacket receipt = mXmppConnectionService.getMessageGenerator().received(account, final var receipt = mXmppConnectionService.getMessageGenerator().received(account,
packet.getFrom(), packet.getFrom(),
remoteMsgId, remoteMsgId,
receiptsNamespaces, receiptsNamespaces,

View file

@ -19,22 +19,21 @@ import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xml.Namespace;
import eu.siacs.conversations.xmpp.InvalidJid; import eu.siacs.conversations.xmpp.InvalidJid;
import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.Jid;
import eu.siacs.conversations.xmpp.OnPresencePacketReceived;
import eu.siacs.conversations.xmpp.pep.Avatar; import eu.siacs.conversations.xmpp.pep.Avatar;
import eu.siacs.conversations.xmpp.stanzas.PresencePacket;
import org.openintents.openpgp.util.OpenPgpUtils; import org.openintents.openpgp.util.OpenPgpUtils;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.function.Consumer;
public class PresenceParser extends AbstractParser implements OnPresencePacketReceived { public class PresenceParser extends AbstractParser implements Consumer<im.conversations.android.xmpp.model.stanza.Presence> {
public PresenceParser(XmppConnectionService service) { public PresenceParser(final XmppConnectionService service, final Account account) {
super(service); super(service, account);
} }
public void parseConferencePresence(PresencePacket packet, Account account) { public void parseConferencePresence(final im.conversations.android.xmpp.model.stanza.Presence packet, Account account) {
final Conversation conversation = final Conversation conversation =
packet.getFrom() == null packet.getFrom() == null
? null ? null
@ -58,9 +57,7 @@ public class PresenceParser extends AbstractParser implements OnPresencePacketRe
} }
} }
private void processConferencePresence(PresencePacket packet, Conversation conversation) { private void processConferencePresence(final im.conversations.android.xmpp.model.stanza.Presence packet, Conversation conversation) {
final Account account = conversation.getAccount(); final Account account = conversation.getAccount();
final MucOptions mucOptions = conversation.getMucOptions(); final MucOptions mucOptions = conversation.getMucOptions();
final Jid jid = conversation.getAccount().getJid(); final Jid jid = conversation.getAccount().getJid();
@ -300,7 +297,7 @@ public class PresenceParser extends AbstractParser implements OnPresencePacketRe
return codes; return codes;
} }
private void parseContactPresence(final PresencePacket packet, final Account account) { private void parseContactPresence(final im.conversations.android.xmpp.model.stanza.Presence packet, final Account account) {
final PresenceGenerator mPresenceGenerator = mXmppConnectionService.getPresenceGenerator(); final PresenceGenerator mPresenceGenerator = mXmppConnectionService.getPresenceGenerator();
final Jid from = packet.getFrom(); final Jid from = packet.getFrom();
if (from == null || from.equals(account.getJid())) { if (from == null || from.equals(account.getJid())) {
@ -434,7 +431,7 @@ public class PresenceParser extends AbstractParser implements OnPresencePacketRe
} }
@Override @Override
public void onPresencePacketReceived(Account account, PresencePacket packet) { public void accept(final im.conversations.android.xmpp.model.stanza.Presence packet) {
if (packet.hasChild("x", Namespace.MUC_USER)) { if (packet.hasChild("x", Namespace.MUC_USER)) {
this.parseConferencePresence(packet, account); this.parseConferencePresence(packet, account);
} else if (packet.hasChild("x", "http://jabber.org/protocol/muc")) { } else if (packet.hasChild("x", "http://jabber.org/protocol/muc")) {

View file

@ -53,6 +53,21 @@ import com.madebyevan.thumbhash.ThumbHash;
import com.wolt.blurhashkt.BlurHashDecoder; import com.wolt.blurhashkt.BlurHashDecoder;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.DownloadableFile;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.services.AttachFileToConversationRunnable;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.ui.adapter.MediaAdapter;
import eu.siacs.conversations.ui.util.Attachment;
import eu.siacs.conversations.utils.CryptoHelper;
import eu.siacs.conversations.utils.FileUtils;
import eu.siacs.conversations.utils.FileWriterException;
import eu.siacs.conversations.utils.MimeUtils;
import eu.siacs.conversations.xmpp.pep.Avatar;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.Closeable; import java.io.Closeable;
@ -85,22 +100,6 @@ import org.tomlj.TomlTable;
import io.ipfs.cid.Cid; import io.ipfs.cid.Cid;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.DownloadableFile;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.services.AttachFileToConversationRunnable;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.ui.adapter.MediaAdapter;
import eu.siacs.conversations.ui.util.Attachment;
import eu.siacs.conversations.utils.CryptoHelper;
import eu.siacs.conversations.utils.FileUtils;
import eu.siacs.conversations.utils.FileWriterException;
import eu.siacs.conversations.utils.MimeUtils;
import eu.siacs.conversations.xmpp.pep.Avatar;
import eu.siacs.conversations.xml.Element;
public class FileBackend { public class FileBackend {
private static final Object THUMBNAIL_LOCK = new Object(); private static final Object THUMBNAIL_LOCK = new Object();
@ -784,16 +783,16 @@ public class FileBackend {
} }
private void copyFileToPrivateStorage(File file, Uri uri) throws FileCopyException { private void copyFileToPrivateStorage(File file, Uri uri) throws FileCopyException {
Log.d( final var parentDirectory = file.getParentFile();
Config.LOGTAG, if (parentDirectory != null && parentDirectory.mkdirs()) {
"copy file (" + uri.toString() + ") to private storage " + file.getAbsolutePath()); Log.d(Config.LOGTAG,"created directory "+parentDirectory.getAbsolutePath());
file.getParentFile().mkdirs(); }
try { try {
if (!file.createNewFile() && file.length() > 0) { if (!file.createNewFile() && file.length() > 0) {
if (file.canRead() && file.getName().startsWith("zb2")) return; // We have this content already if (file.canRead() && file.getName().startsWith("zb2")) return; // We have this content already
throw new FileCopyException(R.string.error_unable_to_create_temporary_file); throw new FileCopyException(R.string.error_unable_to_create_temporary_file);
} }
} catch (IOException e) { } catch (final IOException e) {
throw new FileCopyException(R.string.error_unable_to_create_temporary_file); throw new FileCopyException(R.string.error_unable_to_create_temporary_file);
} }
try (final OutputStream os = new FileOutputStream(file); try (final OutputStream os = new FileOutputStream(file);
@ -803,12 +802,12 @@ public class FileBackend {
} }
try { try {
ByteStreams.copy(is, os); ByteStreams.copy(is, os);
} catch (IOException e) { } catch (final IOException e) {
throw new FileWriterException(file); throw new FileWriterException(file);
} }
try { try {
os.flush(); os.flush();
} catch (IOException e) { } catch (final IOException e) {
throw new FileWriterException(file); throw new FileWriterException(file);
} }
} catch (final FileNotFoundException e) { } catch (final FileNotFoundException e) {
@ -817,7 +816,7 @@ public class FileBackend {
} catch (final FileWriterException e) { } catch (final FileWriterException e) {
cleanup(file); cleanup(file);
throw new FileCopyException(R.string.error_unable_to_create_temporary_file); throw new FileCopyException(R.string.error_unable_to_create_temporary_file);
} catch (final SecurityException | IllegalStateException e) { } catch (final SecurityException | IllegalStateException | IllegalArgumentException e) {
cleanup(file); cleanup(file);
throw new FileCopyException(R.string.error_security_exception); throw new FileCopyException(R.string.error_security_exception);
} catch (final IOException e) { } catch (final IOException e) {
@ -828,7 +827,7 @@ public class FileBackend {
public void copyFileToPrivateStorage(Message message, Uri uri, String type) public void copyFileToPrivateStorage(Message message, Uri uri, String type)
throws FileCopyException { throws FileCopyException {
String mime = MimeUtils.guessMimeTypeFromUriAndMime(mXmppConnectionService, uri, type); final String mime = MimeUtils.guessMimeTypeFromUriAndMime(mXmppConnectionService, uri, type);
Log.d(Config.LOGTAG, "copy " + uri.toString() + " to private storage (mime=" + mime + ")"); Log.d(Config.LOGTAG, "copy " + uri.toString() + " to private storage (mime=" + mime + ")");
String extension = MimeUtils.guessExtensionFromMimeType(mime); String extension = MimeUtils.guessExtensionFromMimeType(mime);
if (extension == null) { if (extension == null) {
@ -1168,9 +1167,9 @@ public class FileBackend {
} }
public BitmapDrawable getFallbackThumbnail(final Message message, int size, boolean cacheOnly) { public BitmapDrawable getFallbackThumbnail(final Message message, int size, boolean cacheOnly) {
List<Element> thumbs = message.getFileParams() != null ? message.getFileParams().getThumbnails() : null; final var thumbs = message.getFileParams() != null ? message.getFileParams().getThumbnails() : null;
if (thumbs != null && !thumbs.isEmpty()) { if (thumbs != null && !thumbs.isEmpty()) {
for (Element thumb : thumbs) { for (final var thumb : thumbs) {
final var uriS = thumb.getAttribute("uri"); final var uriS = thumb.getAttribute("uri");
if (uriS == null) continue; if (uriS == null) continue;
Uri uri = Uri.parse(uriS); Uri uri = Uri.parse(uriS);
@ -1240,9 +1239,9 @@ public class FileBackend {
if ((thumbnail == null) && (!cacheOnly)) { if ((thumbnail == null) && (!cacheOnly)) {
synchronized (THUMBNAIL_LOCK) { synchronized (THUMBNAIL_LOCK) {
List<Element> thumbs = message.getFileParams() != null ? message.getFileParams().getThumbnails() : null; final var thumbs = message.getFileParams() != null ? message.getFileParams().getThumbnails() : null;
if (thumbs != null && !thumbs.isEmpty()) { if (thumbs != null && !thumbs.isEmpty()) {
for (Element thumb : thumbs) { for (final var thumb : thumbs) {
final var uriS = thumb.getAttribute("uri"); final var uriS = thumb.getAttribute("uri");
if (uriS == null) continue; if (uriS == null) continue;
Uri uri = Uri.parse(uriS); Uri uri = Uri.parse(uriS);

View file

@ -42,8 +42,12 @@ public class CallIntegration extends Connection {
* *
* <p>Samsung Galaxy Tab A claims to have FEATURE_CONNECTION_SERVICE but then throws * <p>Samsung Galaxy Tab A claims to have FEATURE_CONNECTION_SERVICE but then throws
* SecurityException when invoking placeCall(). Both Stock and LineageOS have this problem. * SecurityException when invoking placeCall(). Both Stock and LineageOS have this problem.
*
* <p>Lenovo Yoga Smart Tab YT-X705F claims to have FEATURE_CONNECTION_SERVICE but throws
* SecurityException
*/ */
private static final List<String> BROKEN_DEVICE_MODELS = Arrays.asList("OnePlus6", "gtaxlwifi"); private static final List<String> BROKEN_DEVICE_MODELS =
Arrays.asList("OnePlus6", "gtaxlwifi", "YT-X705F");
public static final int DEFAULT_TONE_VOLUME = 60; public static final int DEFAULT_TONE_VOLUME = 60;
private static final int DEFAULT_MEDIA_PLAYER_VOLUME = 90; private static final int DEFAULT_MEDIA_PLAYER_VOLUME = 90;
@ -393,9 +397,7 @@ public class CallIntegration extends Connection {
public void success() { public void success() {
Log.d(Config.LOGTAG, "CallIntegration.success()"); Log.d(Config.LOGTAG, "CallIntegration.success()");
final var toneGenerator = startTone(DEFAULT_TONE_VOLUME, ToneGenerator.TONE_CDMA_CALLDROP_LITE, 375);
new ToneGenerator(AudioManager.STREAM_VOICE_CALL, DEFAULT_TONE_VOLUME);
toneGenerator.startTone(ToneGenerator.TONE_CDMA_CALLDROP_LITE, 375);
this.destroyWithDelay(new DisconnectCause(DisconnectCause.LOCAL, null), 375); this.destroyWithDelay(new DisconnectCause(DisconnectCause.LOCAL, null), 375);
} }
@ -410,9 +412,7 @@ public class CallIntegration extends Connection {
public void error() { public void error() {
Log.d(Config.LOGTAG, "CallIntegration.error()"); Log.d(Config.LOGTAG, "CallIntegration.error()");
final var toneGenerator = startTone(DEFAULT_TONE_VOLUME, ToneGenerator.TONE_CDMA_CALLDROP_LITE, 375);
new ToneGenerator(AudioManager.STREAM_VOICE_CALL, DEFAULT_TONE_VOLUME);
toneGenerator.startTone(ToneGenerator.TONE_CDMA_CALLDROP_LITE, 375);
this.destroyWithDelay(new DisconnectCause(DisconnectCause.ERROR, null), 375); this.destroyWithDelay(new DisconnectCause(DisconnectCause.ERROR, null), 375);
} }
@ -429,8 +429,7 @@ public class CallIntegration extends Connection {
public void busy() { public void busy() {
Log.d(Config.LOGTAG, "CallIntegration.busy()"); Log.d(Config.LOGTAG, "CallIntegration.busy()");
final var toneGenerator = new ToneGenerator(AudioManager.STREAM_VOICE_CALL, 80); startTone(80, ToneGenerator.TONE_CDMA_NETWORK_BUSY, 2500);
toneGenerator.startTone(ToneGenerator.TONE_CDMA_NETWORK_BUSY, 2500);
this.destroyWithDelay(new DisconnectCause(DisconnectCause.BUSY, null), 2500); this.destroyWithDelay(new DisconnectCause(DisconnectCause.BUSY, null), 2500);
} }
@ -458,6 +457,17 @@ public class CallIntegration extends Connection {
Log.d(Config.LOGTAG, "destroyed!"); Log.d(Config.LOGTAG, "destroyed!");
} }
private void startTone(final int volume, final int toneType, final int durationMs) {
final ToneGenerator toneGenerator;
try {
toneGenerator = new ToneGenerator(AudioManager.STREAM_VOICE_CALL, volume);
} catch (final RuntimeException e) {
Log.e(Config.LOGTAG, "could not initialize tone generator", e);
return;
}
toneGenerator.startTone(toneType, durationMs);
}
public static Uri address(final Jid contact) { public static Uri address(final Jid contact) {
return Uri.parse(String.format("xmpp:%s", contact.toEscapedString())); return Uri.parse(String.format("xmpp:%s", contact.toEscapedString()));
} }
@ -532,6 +542,12 @@ public class CallIntegration extends Connection {
&& Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) { && Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
return false; return false;
} }
// we are relatively sure that old Oppo devices are broken too. We get reports of 'number
// not sent' from Oppo R15x (Android 10)
if ("OPPO".equalsIgnoreCase(Build.MANUFACTURER)
&& Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
return false;
}
// we only know of one Umidigi device (BISON_GT2_5G) that doesn't work (audio is not being // we only know of one Umidigi device (BISON_GT2_5G) that doesn't work (audio is not being
// routed properly) However with those devices being extremely rare it's impossible to gauge // routed properly) However with those devices being extremely rare it's impossible to gauge
// how many might be effected and no Naomi Wu around to clarify with the company directly // how many might be effected and no Naomi Wu around to clarify with the company directly

View file

@ -36,6 +36,7 @@ import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.ui.RtpSessionActivity; import eu.siacs.conversations.ui.RtpSessionActivity;
import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.Jid;
import eu.siacs.conversations.xmpp.jingle.AbstractJingleConnection; import eu.siacs.conversations.xmpp.jingle.AbstractJingleConnection;
import eu.siacs.conversations.xmpp.jingle.JingleConnectionManager;
import eu.siacs.conversations.xmpp.jingle.JingleRtpConnection; import eu.siacs.conversations.xmpp.jingle.JingleRtpConnection;
import eu.siacs.conversations.xmpp.jingle.Media; import eu.siacs.conversations.xmpp.jingle.Media;
import eu.siacs.conversations.xmpp.jingle.RtpEndUserState; import eu.siacs.conversations.xmpp.jingle.RtpEndUserState;
@ -126,9 +127,17 @@ public class CallIntegrationConnectionService extends ConnectionService {
// actually attempted // actually attempted
// sendJingleFinishMessage(service, contact, Reason.CONNECTIVITY_ERROR); // sendJingleFinishMessage(service, contact, Reason.CONNECTIVITY_ERROR);
} else { } else {
final var proposal = final JingleConnectionManager.RtpSessionProposal proposal;
try {
proposal =
service.getJingleConnectionManager() service.getJingleConnectionManager()
.proposeJingleRtpSession(account, with, media); .proposeJingleRtpSession(account, with, media);
} catch (final IllegalStateException e) {
return Connection.createFailedConnection(
new DisconnectCause(
DisconnectCause.ERROR,
"Phone is busy. Probably race condition. Try again in a moment"));
}
if (proposal == null) { if (proposal == null) {
// TODO instead of just null checking try to get the sessionID // TODO instead of just null checking try to get the sessionID
return Connection.createFailedConnection( return Connection.createFailedConnection(

View file

@ -1,8 +1,6 @@
package eu.siacs.conversations.services; package eu.siacs.conversations.services;
import static eu.siacs.conversations.utils.Random.SECURE_RANDOM;
import android.os.Build;
import android.util.Log; import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@ -12,17 +10,15 @@ import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheBuilder;
import eu.siacs.conversations.Config; import eu.siacs.conversations.Config;
import eu.siacs.conversations.crypto.TrustManagers;
import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Room; import eu.siacs.conversations.entities.Room;
import eu.siacs.conversations.http.HttpConnectionManager; import eu.siacs.conversations.http.HttpConnectionManager;
import eu.siacs.conversations.http.services.MuclumbusService; import eu.siacs.conversations.http.services.MuclumbusService;
import eu.siacs.conversations.parser.IqParser; import eu.siacs.conversations.parser.IqParser;
import eu.siacs.conversations.utils.TLSSocketFactory;
import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.Jid;
import eu.siacs.conversations.xmpp.OnIqPacketReceived;
import eu.siacs.conversations.xmpp.XmppConnection; import eu.siacs.conversations.xmpp.XmppConnection;
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
import im.conversations.android.xmpp.model.stanza.Iq;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import okhttp3.ResponseBody; import okhttp3.ResponseBody;
@ -34,10 +30,6 @@ import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory; import retrofit2.converter.gson.GsonConverterFactory;
import java.io.IOException; import java.io.IOException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
@ -47,9 +39,6 @@ import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.X509TrustManager;
public class ChannelDiscoveryService { public class ChannelDiscoveryService {
private final XmppConnectionService service; private final XmppConnectionService service;
@ -68,25 +57,7 @@ public class ChannelDiscoveryService {
this.muclumbusService = null; this.muclumbusService = null;
return; return;
} }
final OkHttpClient.Builder builder = HttpConnectionManager.OK_HTTP_CLIENT.newBuilder(); final OkHttpClient.Builder builder = HttpConnectionManager.okHttpClient(service).newBuilder();
try {
final X509TrustManager trustManager;
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N) {
trustManager = TrustManagers.defaultWithBundledLetsEncrypt(service);
} else {
trustManager = TrustManagers.createDefaultTrustManager();
}
final SSLSocketFactory socketFactory =
new TLSSocketFactory(new X509TrustManager[] {trustManager}, SECURE_RANDOM);
builder.sslSocketFactory(socketFactory, trustManager);
} catch (final IOException
| KeyManagementException
| NoSuchAlgorithmException
| KeyStoreException
| CertificateException e) {
Log.d(Config.LOGTAG, "not reconfiguring service to work with bundled LetsEncrypt");
throw new RuntimeException(e);
}
if (service.useTorToConnect()) { if (service.useTorToConnect()) {
builder.proxy(HttpConnectionManager.getProxy()); builder.proxy(HttpConnectionManager.getProxy());
} }
@ -203,7 +174,7 @@ public class ChannelDiscoveryService {
final String query, Map<Jid, Account> mucServices, final OnChannelSearchResultsFound listener) { final String query, Map<Jid, Account> mucServices, final OnChannelSearchResultsFound listener) {
final Map<Jid, Account> localMucService = mucServices == null ? getLocalMucServices() : mucServices; final Map<Jid, Account> localMucService = mucServices == null ? getLocalMucServices() : mucServices;
Log.d(Config.LOGTAG, "checking with " + localMucService.size() + " muc services"); Log.d(Config.LOGTAG, "checking with " + localMucService.size() + " muc services");
if (localMucService.size() == 0) { if (localMucService.isEmpty()) {
listener.onChannelSearchResultsFound(Collections.emptyList()); listener.onChannelSearchResultsFound(Collections.emptyList());
return; return;
} }
@ -217,38 +188,37 @@ public class ChannelDiscoveryService {
} }
final AtomicInteger queriesInFlight = new AtomicInteger(); final AtomicInteger queriesInFlight = new AtomicInteger();
final List<Room> rooms = new ArrayList<>(); final List<Room> rooms = new ArrayList<>();
for (Map.Entry<Jid, Account> entry : localMucService.entrySet()) { for (final Map.Entry<Jid, Account> entry : localMucService.entrySet()) {
IqPacket itemsRequest = service.getIqGenerator().queryDiscoItems(entry.getKey()); Iq itemsRequest = service.getIqGenerator().queryDiscoItems(entry.getKey());
queriesInFlight.incrementAndGet(); queriesInFlight.incrementAndGet();
final var account = entry.getValue();
service.sendIqPacket( service.sendIqPacket(
entry.getValue(), account,
itemsRequest, itemsRequest,
(account, itemsResponse) -> { (itemsResponse) -> {
if (itemsResponse.getType() == IqPacket.TYPE.RESULT) { if (itemsResponse.getType() == Iq.Type.RESULT) {
final List<Jid> items = IqParser.items(itemsResponse); final List<Jid> items = IqParser.items(itemsResponse);
for (Jid item : items) { for (final Jid item : items) {
if (item.isDomainJid()) continue; // Only looking for MUCs for now, and by spec they have a localpart if (item.isDomainJid()) continue; // Only looking for MUCs for now, and by spec they have a localpart
IqPacket infoRequest = final Iq infoRequest =
service.getIqGenerator().queryDiscoInfo(item); service.getIqGenerator().queryDiscoInfo(item);
queriesInFlight.incrementAndGet(); queriesInFlight.incrementAndGet();
service.sendIqPacket( service.sendIqPacket(
account, account,
infoRequest, infoRequest,
new OnIqPacketReceived() { infoResponse -> {
@Override
public void onIqPacketReceived(
Account account, IqPacket infoResponse) {
if (infoResponse.getType() if (infoResponse.getType()
== IqPacket.TYPE.RESULT) { == Iq.Type.RESULT) {
final Room room = final Room room =
IqParser.parseRoom(infoResponse); IqParser.parseRoom(infoResponse);
if (room != null) { if (room != null) {
rooms.add(room); rooms.add(room);
} }
}
if (queriesInFlight.decrementAndGet() <= 0) { if (queriesInFlight.decrementAndGet() <= 0) {
finishDiscoSearch(rooms, query, mucServices, listener); finishDiscoSearch(rooms, query, mucServices, listener);
} }
} else {
queriesInFlight.decrementAndGet();
} }
}, 20L); }, 20L);
} }

View file

@ -41,7 +41,6 @@ import android.util.Log;
import android.util.SparseArray; import android.util.SparseArray;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.core.util.Consumer;
import com.google.common.base.Charsets; import com.google.common.base.Charsets;
import com.google.common.base.Joiner; import com.google.common.base.Joiner;
@ -86,6 +85,7 @@ import java.util.ArrayList;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.function.Consumer;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import java.util.regex.Pattern; import java.util.regex.Pattern;

View file

@ -23,8 +23,8 @@ import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.Jid;
import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded; import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded;
import eu.siacs.conversations.xmpp.mam.MamReference; import eu.siacs.conversations.xmpp.mam.MamReference;
import eu.siacs.conversations.xmpp.stanzas.IqPacket; import im.conversations.android.xmpp.model.stanza.Iq;
import eu.siacs.conversations.xmpp.stanzas.MessagePacket; import im.conversations.android.xmpp.model.stanza.Message;
public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
@ -81,7 +81,7 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
return false; return false;
} }
public static Element findResult(MessagePacket packet) { public static Element findResult(Message packet) {
for (Version version : values()) { for (Version version : values()) {
Element result = packet.findChild("result", version.namespace); Element result = packet.findChild("result", version.namespace);
if (result != null) { if (result != null) {
@ -233,17 +233,17 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
throw new IllegalStateException("Attempted to run MAM query for archived conversation"); throw new IllegalStateException("Attempted to run MAM query for archived conversation");
} }
Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() + ": running mam query " + query.toString()); Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() + ": running mam query " + query.toString());
final IqPacket packet = this.mXmppConnectionService.getIqGenerator().queryMessageArchiveManagement(query); final Iq packet = this.mXmppConnectionService.getIqGenerator().queryMessageArchiveManagement(query);
this.mXmppConnectionService.sendIqPacket(account, packet, (a, p) -> { this.mXmppConnectionService.sendIqPacket(account, packet, (p) -> {
final Element fin = p.findChild("fin", query.version.namespace); final Element fin = p.findChild("fin", query.version.namespace);
if (p.getType() == IqPacket.TYPE.TIMEOUT) { if (p.getType() == Iq.Type.TIMEOUT) {
synchronized (this.queries) { synchronized (this.queries) {
this.queries.remove(query); this.queries.remove(query);
if (query.hasCallback()) { if (query.hasCallback()) {
query.callback(false); query.callback(false);
} }
} }
} else if (p.getType() == IqPacket.TYPE.RESULT && fin != null) { } else if (p.getType() == Iq.Type.RESULT && fin != null) {
final boolean running; final boolean running;
synchronized (this.queries) { synchronized (this.queries) {
running = this.queries.contains(query); running = this.queries.contains(query);
@ -253,10 +253,10 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
} else { } else {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": ignoring MAM iq result because query had been killed"); Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": ignoring MAM iq result because query had been killed");
} }
} else if (p.getType() == IqPacket.TYPE.RESULT && query.isLegacy()) { } else if (p.getType() == Iq.Type.RESULT && query.isLegacy()) {
//do nothing //do nothing
} else { } else {
Log.d(Config.LOGTAG, a.getJid().asBareJid().toString() + ": error executing mam: " + p.toString()); Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() + ": error executing mam: " + p.toString());
try { try {
finalizeQuery(query, true); finalizeQuery(query, true);
} catch (final IllegalStateException e) { } catch (final IllegalStateException e) {
@ -303,7 +303,7 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
} }
} }
boolean inCatchup(Account account) { public boolean inCatchup(Account account) {
synchronized (this.queries) { synchronized (this.queries) {
for (Query query : queries) { for (Query query : queries) {
if (query.account == account && query.isCatchup() && query.getWith() == null) { if (query.account == account && query.isCatchup() && query.getWith() == null) {

View file

@ -270,6 +270,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
popupMenu.show(); popupMenu.show();
return true; return true;
}); });
this.binding.editMucNameButton.setContentDescription(getString(R.string.edit_name_and_topic));
this.binding.editMucNameButton.setOnClickListener(this::onMucEditButtonClicked); this.binding.editMucNameButton.setOnClickListener(this::onMucEditButtonClicked);
this.binding.mucEditTitle.addTextChangedListener(this); this.binding.mucEditTitle.addTextChangedListener(this);
this.binding.mucEditSubject.addTextChangedListener(this); this.binding.mucEditSubject.addTextChangedListener(this);
@ -432,6 +433,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
this.binding.mucEditor.setVisibility(View.GONE); this.binding.mucEditor.setVisibility(View.GONE);
this.binding.mucDisplay.setVisibility(View.VISIBLE); this.binding.mucDisplay.setVisibility(View.VISIBLE);
this.binding.editMucNameButton.setImageResource(R.drawable.ic_edit_24dp); this.binding.editMucNameButton.setImageResource(R.drawable.ic_edit_24dp);
this.binding.editMucNameButton.setContentDescription(getString(R.string.edit_name_and_topic));
} }
private void onMucInfoUpdated(String subject, String name) { private void onMucInfoUpdated(String subject, String name) {
@ -669,7 +671,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
}); });
this.mUserPreviewAdapter.submitList(MucOptions.sub(users, GridManager.getCurrentColumnCount(binding.users))); this.mUserPreviewAdapter.submitList(MucOptions.sub(users, GridManager.getCurrentColumnCount(binding.users)));
this.binding.invite.setVisibility(mucOptions.canInvite() ? View.VISIBLE : View.GONE); this.binding.invite.setVisibility(mucOptions.canInvite() ? View.VISIBLE : View.GONE);
this.binding.showUsers.setVisibility(mucOptions.getUsers(true, mucOptions.getSelf().getAffiliation().ranks(MucOptions.Affiliation.ADMIN)).size() > 0 ? View.VISIBLE : View.GONE); this.binding.showUsers.setVisibility(users.size() > 0 ? View.VISIBLE : View.GONE);
this.binding.showUsers.setText(getResources().getQuantityString(R.plurals.view_users, users.size(), users.size())); this.binding.showUsers.setText(getResources().getQuantityString(R.plurals.view_users, users.size(), users.size()));
this.binding.usersWrapper.setVisibility(users.size() > 0 || mucOptions.canInvite() ? View.VISIBLE : View.GONE); this.binding.usersWrapper.setVisibility(users.size() > 0 || mucOptions.canInvite() ? View.VISIBLE : View.GONE);
if (users.size() == 0) { if (users.size() == 0) {

View file

@ -217,7 +217,8 @@ import eu.siacs.conversations.xmpp.jingle.JingleFileTransferConnection;
import eu.siacs.conversations.xmpp.jingle.Media; import eu.siacs.conversations.xmpp.jingle.Media;
import eu.siacs.conversations.xmpp.jingle.OngoingRtpSession; import eu.siacs.conversations.xmpp.jingle.OngoingRtpSession;
import eu.siacs.conversations.xmpp.jingle.RtpCapability; import eu.siacs.conversations.xmpp.jingle.RtpCapability;
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
import im.conversations.android.xmpp.model.stanza.Iq;
import me.drakeet.support.toast.ToastCompat; import me.drakeet.support.toast.ToastCompat;
@ -3587,13 +3588,13 @@ public class ConversationFragment extends XmppFragment
} else { } else {
if (!delayShow) conversation.showViewPager(); if (!delayShow) conversation.showViewPager();
binding.commandsViewProgressbar.setVisibility(View.VISIBLE); binding.commandsViewProgressbar.setVisibility(View.VISIBLE);
activity.xmppConnectionService.fetchCommands(conversation.getAccount(), commandJid, (a, iq) -> { activity.xmppConnectionService.fetchCommands(conversation.getAccount(), commandJid, (iq) -> {
if (activity == null) return; if (activity == null) return;
activity.runOnUiThread(() -> { activity.runOnUiThread(() -> {
binding.commandsViewProgressbar.setVisibility(View.GONE); binding.commandsViewProgressbar.setVisibility(View.GONE);
commandAdapter.clear(); commandAdapter.clear();
if (iq.getType() == IqPacket.TYPE.RESULT) { if (iq.getType() == Iq.Type.RESULT) {
for (Element child : iq.query().getChildren()) { for (Element child : iq.query().getChildren()) {
if (!"item".equals(child.getName()) || !Namespace.DISCO_ITEMS.equals(child.getNamespace())) continue; if (!"item".equals(child.getName()) || !Namespace.DISCO_ITEMS.equals(child.getNamespace())) continue;
commandAdapter.add(new CommandAdapter.Command0050(child)); commandAdapter.add(new CommandAdapter.Command0050(child));

View file

@ -257,7 +257,8 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
if (ExceptionHelper.checkForCrash(this)) return; if (ExceptionHelper.checkForCrash(this)) return;
if (offerToSetupDiallerIntegration()) return; if (offerToSetupDiallerIntegration()) return;
if (offerToDownloadStickers()) return; if (offerToDownloadStickers()) return;
openBatteryOptimizationDialogIfNeeded(); if (openBatteryOptimizationDialogIfNeeded()) return;
requestNotificationPermissionIfNeeded();
xmppConnectionService.rescanStickers(); xmppConnectionService.rescanStickers();
} }
} }
@ -282,7 +283,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
intent.setData(uri); intent.setData(uri);
try { try {
startActivityForResult(intent, REQUEST_BATTERY_OP); startActivityForResult(intent, REQUEST_BATTERY_OP);
} catch (ActivityNotFoundException e) { } catch (final ActivityNotFoundException e) {
Toast.makeText(this, R.string.device_does_not_support_battery_op, Toast.LENGTH_SHORT).show(); Toast.makeText(this, R.string.device_does_not_support_battery_op, Toast.LENGTH_SHORT).show();
} }
}); });
@ -375,16 +376,16 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
private void notifyFragmentOfBackendConnected(@IdRes int id) { private void notifyFragmentOfBackendConnected(@IdRes int id) {
final Fragment fragment = getFragmentManager().findFragmentById(id); final Fragment fragment = getFragmentManager().findFragmentById(id);
if (fragment instanceof OnBackendConnected) { if (fragment instanceof OnBackendConnected callback) {
((OnBackendConnected) fragment).onBackendConnected(); callback.onBackendConnected();
} }
} }
private void refreshFragment(@IdRes int id) { private void refreshFragment(@IdRes int id) {
final Fragment fragment = getFragmentManager().findFragmentById(id); final Fragment fragment = getFragmentManager().findFragmentById(id);
if (fragment instanceof XmppFragment) { if (fragment instanceof XmppFragment xmppFragment) {
((XmppFragment) fragment).refresh(); xmppFragment.refresh();
if (refreshForNewCaps) ((XmppFragment) fragment).refreshForNewCaps(newCapsJids); if (refreshForNewCaps) xmppFragment.refreshForNewCaps(newCapsJids);
} }
} }

View file

@ -47,6 +47,7 @@ import android.view.ViewGroup;
import android.widget.AdapterView.AdapterContextMenuInfo; import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.databinding.DataBindingUtil; import androidx.databinding.DataBindingUtil;
import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
@ -96,42 +97,36 @@ public class ConversationsOverviewFragment extends XmppFragment {
private FragmentConversationsOverviewBinding binding; private FragmentConversationsOverviewBinding binding;
private ConversationAdapter conversationsAdapter; private ConversationAdapter conversationsAdapter;
private XmppActivity activity; private XmppActivity activity;
private float mSwipeEscapeVelocity = 0f;
private final PendingActionHelper pendingActionHelper = new PendingActionHelper(); private final PendingActionHelper pendingActionHelper = new PendingActionHelper();
private final ItemTouchHelper.SimpleCallback callback = new ItemTouchHelper.SimpleCallback(0,LEFT|RIGHT) { private final ItemTouchHelper.SimpleCallback callback = new ItemTouchHelper.SimpleCallback(0,LEFT|RIGHT) {
@Override @Override
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
//todo maybe we can manually changing the position of the conversation
return false; return false;
} }
@Override @Override
public float getSwipeEscapeVelocity (float defaultValue) { public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder,
return mSwipeEscapeVelocity;
}
@Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder,
float dX, float dY, int actionState, boolean isCurrentlyActive) { float dX, float dY, int actionState, boolean isCurrentlyActive) {
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); if (viewHolder instanceof ConversationAdapter.ConversationViewHolder conversationViewHolder) {
if(actionState != ItemTouchHelper.ACTION_STATE_IDLE){ getDefaultUIUtil().onDraw(c,recyclerView,conversationViewHolder.binding.frame,dX,dY,actionState,isCurrentlyActive);
Paint paint = new Paint();
paint.setColor(MaterialColors.getColor(viewHolder.itemView, com.google.android.material.R.attr.colorSecondaryFixedDim));
paint.setStyle(Paint.Style.FILL);
c.drawRect(viewHolder.itemView.getLeft(),viewHolder.itemView.getTop()
,viewHolder.itemView.getRight(),viewHolder.itemView.getBottom(), paint);
} }
} }
@Override @Override
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder); if (viewHolder instanceof ConversationAdapter.ConversationViewHolder conversationViewHolder) {
viewHolder.itemView.setAlpha(1f); getDefaultUIUtil().clearView(conversationViewHolder.binding.frame);
}
} }
@Override @Override
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { public float getSwipeEscapeVelocity(final float defaultEscapeVelocity) {
return 32 * defaultEscapeVelocity;
}
@Override
public void onSwiped(final RecyclerView.ViewHolder viewHolder, final int direction) {
pendingActionHelper.execute(); pendingActionHelper.execute();
int position = viewHolder.getLayoutPosition(); int position = viewHolder.getLayoutPosition();
try { try {
@ -291,7 +286,6 @@ public class ConversationsOverviewFragment extends XmppFragment {
@Override @Override
public View onCreateView(final LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { public View onCreateView(final LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
this.mSwipeEscapeVelocity = getResources().getDimension(R.dimen.swipe_escape_velocity);
this.binding = DataBindingUtil.inflate(inflater, R.layout.fragment_conversations_overview, container, false); this.binding = DataBindingUtil.inflate(inflater, R.layout.fragment_conversations_overview, container, false);
this.binding.fab.setOnClickListener((view) -> StartConversationActivity.launch(getActivity())); this.binding.fab.setOnClickListener((view) -> StartConversationActivity.launch(getActivity()));

View file

@ -34,6 +34,7 @@ import androidx.databinding.DataBindingUtil;
import com.google.common.base.Optional; import com.google.common.base.Optional;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.common.base.Throwables; import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
@ -363,10 +364,15 @@ public class RtpSessionActivity extends XmppActivity
private void acceptContentAdd() { private void acceptContentAdd() {
try { try {
requireRtpConnection() final ContentAddition pendingContentAddition =
.acceptContentAdd(requireRtpConnection().getPendingContentAddition().summary); requireRtpConnection().getPendingContentAddition();
if (pendingContentAddition == null) {
Log.d(Config.LOGTAG, "content offer was gone after granting permission");
return;
}
requireRtpConnection().acceptContentAdd(pendingContentAddition.summary);
} catch (final IllegalStateException e) { } catch (final IllegalStateException e) {
Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show(); Toast.makeText(this, Strings.nullToEmpty(e.getMessage()), Toast.LENGTH_SHORT).show();
} }
} }
@ -537,7 +543,12 @@ public class RtpSessionActivity extends XmppActivity
final String action = intent.getAction(); final String action = intent.getAction();
Log.d(Config.LOGTAG, "initializeWithIntent(" + event + "," + action + ")"); Log.d(Config.LOGTAG, "initializeWithIntent(" + event + "," + action + ")");
final Account account = extractAccount(intent); final Account account = extractAccount(intent);
final Jid with = Jid.ofEscaped(intent.getStringExtra(EXTRA_WITH)); final var extraWith = intent.getStringExtra(EXTRA_WITH);
final Jid with = Strings.isNullOrEmpty(extraWith) ? null : Jid.ofEscaped(extraWith);
if (with == null || account == null) {
Log.e(Config.LOGTAG, "intent is missing extras (account or with)");
return;
}
final String sessionId = intent.getStringExtra(EXTRA_SESSION_ID); final String sessionId = intent.getStringExtra(EXTRA_SESSION_ID);
if (sessionId != null) { if (sessionId != null) {
if (initializeActivityWithRunningRtpSession(account, with, sessionId)) { if (initializeActivityWithRunningRtpSession(account, with, sessionId)) {
@ -1089,16 +1100,21 @@ public class RtpSessionActivity extends XmppActivity
final CallIntegration.AudioDevice selectedAudioDevice, final int numberOfChoices) { final CallIntegration.AudioDevice selectedAudioDevice, final int numberOfChoices) {
switch (selectedAudioDevice) { switch (selectedAudioDevice) {
case EARPIECE -> { case EARPIECE -> {
this.binding.inCallActionRight.setImageResource( this.binding.inCallActionRight.setImageResource(R.drawable.ic_volume_off_24dp);
R.drawable.ic_volume_off_24dp);
if (numberOfChoices >= 2) { if (numberOfChoices >= 2) {
this.binding.inCallActionRight.setContentDescription(
getString(R.string.call_is_using_earpiece_tap_to_switch_to_speaker));
this.binding.inCallActionRight.setOnClickListener(this::switchToSpeaker); this.binding.inCallActionRight.setOnClickListener(this::switchToSpeaker);
} else { } else {
this.binding.inCallActionRight.setContentDescription(
getString(R.string.call_is_using_earpiece));
this.binding.inCallActionRight.setOnClickListener(null); this.binding.inCallActionRight.setOnClickListener(null);
this.binding.inCallActionRight.setClickable(false); this.binding.inCallActionRight.setClickable(false);
} }
} }
case WIRED_HEADSET -> { case WIRED_HEADSET -> {
this.binding.inCallActionRight.setContentDescription(
getString(R.string.call_is_using_wired_headset));
this.binding.inCallActionRight.setImageResource(R.drawable.ic_headset_mic_24dp); this.binding.inCallActionRight.setImageResource(R.drawable.ic_headset_mic_24dp);
this.binding.inCallActionRight.setOnClickListener(null); this.binding.inCallActionRight.setOnClickListener(null);
this.binding.inCallActionRight.setClickable(false); this.binding.inCallActionRight.setClickable(false);
@ -1106,15 +1122,20 @@ public class RtpSessionActivity extends XmppActivity
case SPEAKER_PHONE -> { case SPEAKER_PHONE -> {
this.binding.inCallActionRight.setImageResource(R.drawable.ic_volume_up_24dp); this.binding.inCallActionRight.setImageResource(R.drawable.ic_volume_up_24dp);
if (numberOfChoices >= 2) { if (numberOfChoices >= 2) {
this.binding.inCallActionRight.setContentDescription(
getString(R.string.call_is_using_speaker_tap_to_switch_to_earpiece));
this.binding.inCallActionRight.setOnClickListener(this::switchToEarpiece); this.binding.inCallActionRight.setOnClickListener(this::switchToEarpiece);
} else { } else {
this.binding.inCallActionRight.setContentDescription(
getString(R.string.call_is_using_speaker));
this.binding.inCallActionRight.setOnClickListener(null); this.binding.inCallActionRight.setOnClickListener(null);
this.binding.inCallActionRight.setClickable(false); this.binding.inCallActionRight.setClickable(false);
} }
} }
case BLUETOOTH -> { case BLUETOOTH -> {
this.binding.inCallActionRight.setImageResource( this.binding.inCallActionRight.setContentDescription(
R.drawable.ic_bluetooth_audio_24dp); getString(R.string.call_is_using_bluetooth));
this.binding.inCallActionRight.setImageResource(R.drawable.ic_bluetooth_audio_24dp);
this.binding.inCallActionRight.setOnClickListener(null); this.binding.inCallActionRight.setOnClickListener(null);
this.binding.inCallActionRight.setClickable(false); this.binding.inCallActionRight.setClickable(false);
} }
@ -1131,15 +1152,21 @@ public class RtpSessionActivity extends XmppActivity
R.drawable.ic_flip_camera_android_24dp); R.drawable.ic_flip_camera_android_24dp);
this.binding.inCallActionFarRight.setVisibility(View.VISIBLE); this.binding.inCallActionFarRight.setVisibility(View.VISIBLE);
this.binding.inCallActionFarRight.setOnClickListener(this::switchCamera); this.binding.inCallActionFarRight.setOnClickListener(this::switchCamera);
this.binding.inCallActionFarRight.setContentDescription(
getString(R.string.flip_camera));
} else { } else {
this.binding.inCallActionFarRight.setVisibility(View.GONE); this.binding.inCallActionFarRight.setVisibility(View.GONE);
} }
if (videoEnabled) { if (videoEnabled) {
this.binding.inCallActionRight.setImageResource(R.drawable.ic_videocam_24dp); this.binding.inCallActionRight.setImageResource(R.drawable.ic_videocam_24dp);
this.binding.inCallActionRight.setOnClickListener(this::disableVideo); this.binding.inCallActionRight.setOnClickListener(this::disableVideo);
this.binding.inCallActionRight.setContentDescription(
getString(R.string.video_is_enabled_tap_to_disable));
} else { } else {
this.binding.inCallActionRight.setImageResource(R.drawable.ic_videocam_off_24dp); this.binding.inCallActionRight.setImageResource(R.drawable.ic_videocam_off_24dp);
this.binding.inCallActionRight.setOnClickListener(this::enableVideo); this.binding.inCallActionRight.setOnClickListener(this::enableVideo);
this.binding.inCallActionRight.setContentDescription(
getString(R.string.video_is_disabled_tap_to_enable));
} }
} }
@ -1331,13 +1358,21 @@ public class RtpSessionActivity extends XmppActivity
} }
private void switchToEarpiece(final View view) { private void switchToEarpiece(final View view) {
try {
requireCallIntegration().setAudioDevice(CallIntegration.AudioDevice.EARPIECE); requireCallIntegration().setAudioDevice(CallIntegration.AudioDevice.EARPIECE);
acquireProximityWakeLock(); acquireProximityWakeLock();
} catch (final IllegalStateException e) {
Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
}
} }
private void switchToSpeaker(final View view) { private void switchToSpeaker(final View view) {
try {
requireCallIntegration().setAudioDevice(CallIntegration.AudioDevice.SPEAKER_PHONE); requireCallIntegration().setAudioDevice(CallIntegration.AudioDevice.SPEAKER_PHONE);
releaseProximityWakeLock(); releaseProximityWakeLock();
} catch (final IllegalStateException e) {
Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
}
} }
private void retry(final View view) { private void retry(final View view) {

View file

@ -39,7 +39,6 @@ import android.widget.AutoCompleteTextView;
import android.widget.CheckBox; import android.widget.CheckBox;
import android.widget.EditText; import android.widget.EditText;
import android.widget.ListView; import android.widget.ListView;
import android.widget.Spinner;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
@ -50,7 +49,7 @@ import androidx.annotation.StringRes;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.PopupMenu; import androidx.appcompat.widget.PopupMenu;
import androidx.core.content.ContextCompat; import androidx.core.app.ActivityCompat;
import androidx.databinding.DataBindingUtil; import androidx.databinding.DataBindingUtil;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager; 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.color.MaterialColors;
import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.android.material.textfield.TextInputLayout; import com.google.android.material.textfield.TextInputLayout;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables; import com.google.common.collect.Iterables;
import com.leinardi.android.speeddial.SpeedDialActionItem; import com.leinardi.android.speeddial.SpeedDialActionItem;
import com.leinardi.android.speeddial.SpeedDialView; 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.BuildConfig;
import eu.siacs.conversations.Config; import eu.siacs.conversations.Config;
import eu.siacs.conversations.R; 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.OnUpdateBlocklist;
import eu.siacs.conversations.xmpp.XmppConnection; import eu.siacs.conversations.xmpp.XmppConnection;
import eu.siacs.conversations.xmpp.forms.Data; 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"; 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 final AtomicBoolean mOpenedFab = new AtomicBoolean(false);
private boolean mHideOfflineContacts = false; private boolean mHideOfflineContacts = false;
private boolean createdByViewIntent = false; private boolean createdByViewIntent = false;
private final MenuItem.OnActionExpandListener mOnActionExpandListener = new MenuItem.OnActionExpandListener() { private final MenuItem.OnActionExpandListener mOnActionExpandListener =
new MenuItem.OnActionExpandListener() {
@Override @Override
public boolean onMenuItemActionExpand(MenuItem item) { public boolean onMenuItemActionExpand(@NonNull final MenuItem item) {
mSearchEditText.post(() -> { mSearchEditText.post(
() -> {
updateSearchViewHint(); updateSearchViewHint();
mSearchEditText.requestFocus(); mSearchEditText.requestFocus();
if (oneShotKeyboardSuppress.compareAndSet(true, false)) { if (oneShotKeyboardSuppress.compareAndSet(true, false)) {
return; return;
} }
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); InputMethodManager imm =
(InputMethodManager)
getSystemService(Context.INPUT_METHOD_SERVICE);
if (imm != null) { if (imm != null) {
imm.showSoftInput(mSearchEditText, InputMethodManager.SHOW_IMPLICIT); imm.showSoftInput(
mSearchEditText, InputMethodManager.SHOW_IMPLICIT);
} }
}); });
if (binding.speedDial.isOpen()) { if (binding.speedDial.isOpen()) {
@ -161,7 +173,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
} }
@Override @Override
public boolean onMenuItemActionCollapse(MenuItem item) { public boolean onMenuItemActionCollapse(@NonNull final MenuItem item) {
SoftKeyboardUtils.hideSoftKeyboard(StartConversationActivity.this); SoftKeyboardUtils.hideSoftKeyboard(StartConversationActivity.this);
mSearchEditText.setText(""); mSearchEditText.setText("");
filter(null); filter(null);
@ -169,7 +181,8 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
return true; return true;
} }
}; };
private final TextWatcher mSearchTextWatcher = new TextWatcher() { private final TextWatcher mSearchTextWatcher =
new TextWatcher() {
@Override @Override
public void afterTextChanged(Editable editable) { public void afterTextChanged(Editable editable) {
@ -177,15 +190,14 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
} }
@Override @Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) { public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
}
@Override @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 MenuItem mMenuSearchView;
private final ListItemAdapter.OnTagClickedListener mOnTagClickedListener = new ListItemAdapter.OnTagClickedListener() { private final ListItemAdapter.OnTagClickedListener mOnTagClickedListener =
new ListItemAdapter.OnTagClickedListener() {
@Override @Override
public void onTagClicked(String tag) { public void onTagClicked(String tag) {
if (mMenuSearchView != null) { if (mMenuSearchView != null) {
@ -198,10 +210,12 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
}; };
private Pair<Integer, Intent> mPostponedActivityResult; private Pair<Integer, Intent> mPostponedActivityResult;
private Toast mToast; private Toast mToast;
private final UiCallback<Conversation> mAdhocConferenceCallback = new UiCallback<Conversation>() { private final UiCallback<Conversation> mAdhocConferenceCallback =
new UiCallback<>() {
@Override @Override
public void success(final Conversation conversation) { public void success(final Conversation conversation) {
runOnUiThread(() -> { runOnUiThread(
() -> {
hideToast(); hideToast();
switchToConversation(conversation); switchToConversation(conversation);
}); });
@ -213,12 +227,11 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
} }
@Override @Override
public void userInputRequired(PendingIntent pi, Conversation object) { public void userInputRequired(PendingIntent pi, Conversation object) {}
}
}; };
private ActivityStartConversationBinding binding; private ActivityStartConversationBinding binding;
private final TextView.OnEditorActionListener mSearchDone = new TextView.OnEditorActionListener() { private final TextView.OnEditorActionListener mSearchDone =
new TextView.OnEditorActionListener() {
@Override @Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
int pos = binding.startConversationViewPager.getCurrentItem(); int pos = binding.startConversationViewPager.getCurrentItem();
@ -226,7 +239,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
if (contacts.size() == 1) { if (contacts.size() == 1) {
openConversation(contacts.get(0)); openConversation(contacts.get(0));
return true; return true;
} else if (contacts.size() == 0 && conferences.size() == 1) { } else if (contacts.isEmpty() && conferences.size() == 1) {
openConversationsForBookmark((Bookmark) conferences.get(0)); openConversationsForBookmark((Bookmark) conferences.get(0));
return true; return true;
} }
@ -234,7 +247,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
if (conferences.size() == 1) { if (conferences.size() == 1) {
openConversationsForBookmark((Bookmark) conferences.get(0)); openConversationsForBookmark((Bookmark) conferences.get(0));
return true; return true;
} else if (conferences.size() == 0 && contacts.size() == 1) { } else if (conferences.isEmpty() && contacts.size() == 1) {
openConversation(contacts.get(0)); openConversation(contacts.get(0));
return true; 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()) { if (accounts.isEmpty()) {
ArrayAdapter<String> adapter = new ArrayAdapter<>(context, ArrayAdapter<String> adapter =
new ArrayAdapter<>(
context,
R.layout.item_autocomplete, R.layout.item_autocomplete,
Collections.singletonList(context.getString(R.string.no_accounts))); Collections.singletonList(context.getString(R.string.no_accounts)));
adapter.setDropDownViewResource(R.layout.item_autocomplete); adapter.setDropDownViewResource(R.layout.item_autocomplete);
spinner.setAdapter(adapter); spinner.setAdapter(adapter);
spinner.setEnabled(false); spinner.setEnabled(false);
} else { } 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); adapter.setDropDownViewResource(R.layout.item_autocomplete);
spinner.setAdapter(adapter); spinner.setAdapter(adapter);
spinner.setEnabled(true); spinner.setEnabled(true);
@ -275,7 +294,10 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
} }
private static boolean isViewIntent(final Intent i) { 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() { protected void hideToast() {
@ -305,7 +327,8 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
inflateFab(binding.speedDial, R.menu.start_conversation_fab_submenu); inflateFab(binding.speedDial, R.menu.start_conversation_fab_submenu);
binding.tabLayout.setupWithViewPager(binding.startConversationViewPager); binding.tabLayout.setupWithViewPager(binding.startConversationViewPager);
binding.startConversationViewPager.addOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() { binding.startConversationViewPager.addOnPageChangeListener(
new ViewPager.SimpleOnPageChangeListener() {
@Override @Override
public void onPageSelected(int position) { public void onPageSelected(int position) {
updateSearchViewHint(); updateSearchViewHint();
@ -320,9 +343,13 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
final SharedPreferences preferences = getPreferences(); 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; final Intent intent;
if (savedInstanceState == null) { if (savedInstanceState == null) {
@ -347,6 +374,66 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
} else if (startSearching && mInitialSearchValue.peek() == null) { } else if (startSearching && mInitialSearchValue.peek() == null) {
mInitialSearchValue.push(""); 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)); mRequestedContactsPermission.set(savedInstanceState != null && savedInstanceState.getBoolean("requested_contacts_permission", false));
mOpenedFab.set(savedInstanceState != null && savedInstanceState.getBoolean("opened_fab", false)); mOpenedFab.set(savedInstanceState != null && savedInstanceState.getBoolean("opened_fab", false));
binding.speedDial.setOnActionSelectedListener(actionItem -> { binding.speedDial.setOnActionSelectedListener(actionItem -> {
@ -410,16 +497,29 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
final Menu menu = popupMenu.getMenu(); final Menu menu = popupMenu.getMenu();
for (int i = 0; i < menu.size(); i++) { for (int i = 0; i < menu.size(); i++) {
final MenuItem menuItem = menu.getItem(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; continue;
} }
final SpeedDialActionItem actionItem = new SpeedDialActionItem.Builder(menuItem.getItemId(), menuItem.getIcon()) final SpeedDialActionItem actionItem =
.setLabel(menuItem.getTitle() != null ? menuItem.getTitle().toString() : null) new SpeedDialActionItem.Builder(menuItem.getItemId(), menuItem.getIcon())
.setFabImageTintColor(MaterialColors.getColor(speedDialView, com.google.android.material.R.attr.colorOnSurface)) .setLabel(
.setFabBackgroundColor(MaterialColors.getColor(speedDialView, com.google.android.material.R.attr.colorSurfaceContainerHighest)) 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(); .create();
speedDialView.addActionItem(actionItem); speedDialView.addActionItem(actionItem);
} }
speedDialView.setContentDescription(getString(R.string.add_contact_or_create_or_join_group_chat));
} }
public static boolean isValidJid(String input) { public static boolean isValidJid(String input) {
@ -434,12 +534,16 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
@Override @Override
public void onSaveInstanceState(Bundle savedInstanceState) { public void onSaveInstanceState(Bundle savedInstanceState) {
Intent pendingIntent = pendingViewIntent.peek(); Intent pendingIntent = pendingViewIntent.peek();
savedInstanceState.putParcelable("intent", pendingIntent != null ? pendingIntent : getIntent()); savedInstanceState.putParcelable(
savedInstanceState.putBoolean("requested_contacts_permission", mRequestedContactsPermission.get()); "intent", pendingIntent != null ? pendingIntent : getIntent());
savedInstanceState.putBoolean(
"requested_contacts_permission", mRequestedContactsPermission.get());
savedInstanceState.putBoolean("opened_fab", mOpenedFab.get()); savedInstanceState.putBoolean("opened_fab", mOpenedFab.get());
savedInstanceState.putBoolean("created_by_view_intent", createdByViewIntent); savedInstanceState.putBoolean("created_by_view_intent", createdByViewIntent);
if (mMenuSearchView != null && mMenuSearchView.isActionViewExpanded()) { 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); super.onSaveInstanceState(savedInstanceState);
} }
@ -447,11 +551,24 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
@Override @Override
public void onStart() { public void onStart() {
super.onStart(); super.onStart();
if (pendingViewIntent.peek() == null) {
askForContactsPermissions();
}
mConferenceAdapter.refreshSettings(); mConferenceAdapter.refreshSettings();
mContactsAdapter.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 bottomNavigationView=findViewById(R.id.bottom_navigation);
bottomNavigationView.setSelectedItemId(R.id.contactslist); bottomNavigationView.setSelectedItemId(R.id.contactslist);
@ -487,7 +604,9 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
} }
protected void openConversationForContact(Contact contact) { 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); SoftKeyboardUtils.hideSoftKeyboard(this);
switchToConversation(conversation); switchToConversation(conversation);
} }
@ -512,9 +631,11 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
shareIntent.putExtra(Intent.EXTRA_TEXT, "xmpp:" + Uri.encode(address, "@/+") + "?join"); shareIntent.putExtra(Intent.EXTRA_TEXT, "xmpp:" + Uri.encode(address, "@/+") + "?join");
shareIntent.setType("text/plain"); shareIntent.setType("text/plain");
try { 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) { } 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(); Toast.makeText(this, R.string.invalid_jid, Toast.LENGTH_SHORT).show();
return; 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); bookmark.setConversation(conversation);
if (!bookmark.autojoin()) { if (!bookmark.autojoin()) {
bookmark.setAutojoin(true); bookmark.setAutojoin(true);
@ -551,8 +674,12 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this); final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
builder.setNegativeButton(R.string.cancel, null); builder.setNegativeButton(R.string.cancel, null);
builder.setTitle(R.string.action_delete_contact); builder.setTitle(R.string.action_delete_contact);
builder.setMessage(JidDialog.style(this, R.string.remove_contact_text, contact.getJid().toEscapedString())); builder.setMessage(
builder.setPositiveButton(R.string.delete, (dialog, which) -> { JidDialog.style(
this, R.string.remove_contact_text, contact.getJid().toEscapedString()));
builder.setPositiveButton(
R.string.delete,
(dialog, which) -> {
xmppConnectionService.deleteContactOnServer(contact); xmppConnectionService.deleteContactOnServer(contact);
filter(mSearchEditText.getText().toString()); filter(mSearchEditText.getText().toString());
}); });
@ -567,11 +694,19 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
builder.setNegativeButton(R.string.cancel, null); builder.setNegativeButton(R.string.cancel, null);
builder.setTitle(R.string.delete_bookmark); builder.setTitle(R.string.delete_bookmark);
if (hasConversation) { 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 { } 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); bookmark.setConversation(null);
final Account account = bookmark.getAccount(); final Account account = bookmark.getAccount();
xmppConnectionService.deleteBookmark(account, bookmark); xmppConnectionService.deleteBookmark(account, bookmark);
@ -581,7 +716,6 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
filter(mSearchEditText.getText().toString()); filter(mSearchEditText.getText().toString());
}); });
builder.create().show(); builder.create().show();
} }
@SuppressLint("InflateParams") @SuppressLint("InflateParams")
@ -678,7 +812,8 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
ft.remove(prev); ft.remove(prev);
} }
ft.addToBackStack(null); 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); joinConferenceFragment.show(ft, FRAGMENT_TAG_DIALOG);
} }
@ -689,7 +824,8 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
ft.remove(prev); ft.remove(prev);
} }
ft.addToBackStack(null); ft.addToBackStack(null);
CreatePrivateGroupChatDialog createConferenceFragment = CreatePrivateGroupChatDialog.newInstance(mActivatedAccounts); CreatePrivateGroupChatDialog createConferenceFragment =
CreatePrivateGroupChatDialog.newInstance(mActivatedAccounts);
createConferenceFragment.show(ft, FRAGMENT_TAG_DIALOG); createConferenceFragment.show(ft, FRAGMENT_TAG_DIALOG);
} }
@ -700,11 +836,13 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
ft.remove(prev); ft.remove(prev);
} }
ft.addToBackStack(null); ft.addToBackStack(null);
CreatePublicChannelDialog dialog = CreatePublicChannelDialog.newInstance(mActivatedAccounts); CreatePublicChannelDialog dialog =
CreatePublicChannelDialog.newInstance(mActivatedAccounts);
dialog.show(ft, FRAGMENT_TAG_DIALOG); 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()) { if (spinner == null || !spinner.isEnabled()) {
return null; return null;
} }
@ -726,7 +864,9 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
} }
protected void switchToConversation(Contact contact) { 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); switchToConversation(conversation);
} }
@ -735,7 +875,9 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
} }
protected void switchToConversationDoNotAppend(Contact contact, String body, String postInit) { 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); switchToConversation(conversation, body, false, null, false, true, postInit);
} }
@ -881,7 +1023,8 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
this.mPostponedActivityResult = null; this.mPostponedActivityResult = null;
if (requestCode == REQUEST_CREATE_CONFERENCE) { if (requestCode == REQUEST_CREATE_CONFERENCE) {
Account account = extractAccount(intent); 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); final List<Jid> jids = ChooseContactActivity.extractJabberIds(intent);
if (account != null && jids.size() > 0) { if (account != null && jids.size() > 0) {
// This hardcodes cheogram.com and is in general a terrible hack // 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); super.onActivityResult(requestCode, requestCode, intent);
} }
private void askForContactsPermissions() { private boolean askForContactsPermissions() {
if (QuickConversationsService.isContactListIntegration(this)) { if (!QuickConversationsService.isContactListIntegration(this)) {
return false;
}
if (checkSelfPermission(Manifest.permission.READ_CONTACTS) if (checkSelfPermission(Manifest.permission.READ_CONTACTS)
!= PackageManager.PERMISSION_GRANTED) { == PackageManager.PERMISSION_GRANTED) {
return false;
}
if (mRequestedContactsPermission.compareAndSet(false, true)) { 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 = final String consent =
PreferenceManager.getDefaultSharedPreferences(getApplicationContext()) PreferenceManager.getDefaultSharedPreferences(getApplicationContext())
.getString(PREF_KEY_CONTACT_INTEGRATION_CONSENT, null); .getString(PREF_KEY_CONTACT_INTEGRATION_CONSENT, null);
@ -929,19 +1082,19 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|| QuickConversationsService.isPlayStoreFlavor()) || QuickConversationsService.isPlayStoreFlavor())
&& !"agreed".equals(consent); && !"agreed".equals(consent);
if (requiresConsent && "declined".equals(consent)) { if (requiresConsent && "declined".equals(consent)) {
Log.d(Config.LOGTAG,"not asking for contacts permission because consent has been declined"); Log.d(
return; Config.LOGTAG,
"not asking for contacts permission because consent has been declined");
return false;
} }
if (requiresConsent if (requiresConsent
|| shouldShowRequestPermissionRationale( || shouldShowRequestPermissionRationale(Manifest.permission.READ_CONTACTS)) {
Manifest.permission.READ_CONTACTS)) {
final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this); final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this);
final AtomicBoolean requestPermission = new AtomicBoolean(false); final AtomicBoolean requestPermission = new AtomicBoolean(false);
if (QuickConversationsService.isQuicksy()) { if (QuickConversationsService.isQuicksy()) {
builder.setTitle(R.string.quicksy_wants_your_consent); builder.setTitle(R.string.quicksy_wants_your_consent);
builder.setMessage( builder.setMessage(
Html.fromHtml( Html.fromHtml(getString(R.string.sync_with_contacts_quicksy_static)));
getString(R.string.sync_with_contacts_quicksy_static)));
} else { } else {
builder.setTitle(R.string.sync_with_contacts); builder.setTitle(R.string.sync_with_contacts);
builder.setMessage( builder.setMessage(
@ -962,32 +1115,29 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
PreferenceManager.getDefaultSharedPreferences( PreferenceManager.getDefaultSharedPreferences(
getApplicationContext()) getApplicationContext())
.edit() .edit()
.putString( .putString(PREF_KEY_CONTACT_INTEGRATION_CONSENT, "agreed")
PREF_KEY_CONTACT_INTEGRATION_CONSENT, "agreed")
.apply(); .apply();
} }
if (requestPermission.compareAndSet(false, true)) { if (requestPermission.compareAndSet(false, true)) {
requestPermissions( requestPermissions(permission, REQUEST_SYNC_CONTACTS);
new String[] {Manifest.permission.READ_CONTACTS},
REQUEST_SYNC_CONTACTS);
} }
}); });
if (requiresConsent) { if (requiresConsent) {
builder.setNegativeButton(R.string.decline, (dialog, which) -> PreferenceManager.getDefaultSharedPreferences( builder.setNegativeButton(
R.string.decline,
(dialog, which) ->
PreferenceManager.getDefaultSharedPreferences(
getApplicationContext()) getApplicationContext())
.edit() .edit()
.putString( .putString(
PREF_KEY_CONTACT_INTEGRATION_CONSENT, "declined") PREF_KEY_CONTACT_INTEGRATION_CONSENT,
"declined")
.apply()); .apply());
} else { } else {
builder.setOnDismissListener( builder.setOnDismissListener(
dialog -> { dialog -> {
if (requestPermission.compareAndSet(false, true)) { if (requestPermission.compareAndSet(false, true)) {
requestPermissions( requestPermissions(permission, REQUEST_SYNC_CONTACTS);
new String[] {
Manifest.permission.READ_CONTACTS
},
REQUEST_SYNC_CONTACTS);
} }
}); });
} }
@ -1003,17 +1153,15 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
}); });
dialog.show(); dialog.show();
} else { } else {
requestPermissions( requestPermissions(permission, REQUEST_SYNC_CONTACTS);
new String[] {Manifest.permission.READ_CONTACTS},
REQUEST_SYNC_CONTACTS);
}
}
} }
} }
return true;
} }
@Override @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); super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (grantResults.length > 0) if (grantResults.length > 0)
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
@ -1033,11 +1181,10 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
if (actionBar == null) { if (actionBar == null) {
return; return;
} }
boolean openConversations = !createdByViewIntent && !xmppConnectionService.isConversationsListEmpty(null); boolean openConversations =
boolean showNavBar = binding.bottomNavigation.getVisibility() == VISIBLE; !createdByViewIntent && !xmppConnectionService.isConversationsListEmpty(null);
actionBar.setDisplayHomeAsUpEnabled(openConversations && !showNavBar); actionBar.setDisplayHomeAsUpEnabled(openConversations);
actionBar.setDisplayHomeAsUpEnabled(openConversations && !showNavBar); actionBar.setDisplayHomeAsUpEnabled(openConversations);
} }
@Override @Override
@ -1049,7 +1196,8 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
xmppConnectionService.getQuickConversationsService().considerSyncBackground(false); xmppConnectionService.getQuickConversationsService().considerSyncBackground(false);
} }
if (mPostponedActivityResult != null) { if (mPostponedActivityResult != null) {
onActivityResult(mPostponedActivityResult.first, RESULT_OK, mPostponedActivityResult.second); onActivityResult(
mPostponedActivityResult.first, RESULT_OK, mPostponedActivityResult.second);
this.mPostponedActivityResult = null; this.mPostponedActivityResult = null;
} }
this.mActivatedAccounts.clear(); this.mActivatedAccounts.clear();
@ -1118,7 +1266,11 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
if (QuickConversationsService.isQuicksy()) { if (QuickConversationsService.isQuicksy()) {
setRefreshing(xmppConnectionService.getQuickConversationsService().isSynchronizing()); 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(); binding.speedDial.open();
} }
} }
@ -1141,7 +1293,8 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
case Intent.ACTION_VIEW: case Intent.ACTION_VIEW:
Uri uri = intent.getData(); Uri uri = intent.getData();
if (uri != null) { 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.account = intent.getStringExtra(EXTRA_ACCOUNT);
invite.forceDialog = intent.getBooleanExtra("force_dialog", false); invite.forceDialog = intent.getBooleanExtra("force_dialog", false);
return invite.invite(); return invite.invite();
@ -1153,7 +1306,8 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
} }
private boolean handleJid(Invite invite) { 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); final Conversation muc = xmppConnectionService.findFirstMuc(invite.getJid(), invite.account);
if (invite.isAction(XmppUri.ACTION_JOIN) || (contacts.isEmpty() && muc != null)) { if (invite.isAction(XmppUri.ACTION_JOIN) || (contacts.isEmpty() && muc != null)) {
if (muc != null && !invite.forceDialog) { if (muc != null && !invite.forceDialog) {
@ -1175,8 +1329,10 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
displayVerificationWarningDialog(contact, invite); displayVerificationWarningDialog(contact, invite);
} else { } else {
if (invite.hasFingerprints()) { if (invite.hasFingerprints()) {
if (xmppConnectionService.verifyFingerprints(contact, invite.getFingerprints())) { if (xmppConnectionService.verifyFingerprints(
Toast.makeText(this, R.string.verified_fingerprints, Toast.LENGTH_SHORT).show(); contact, invite.getFingerprints())) {
Toast.makeText(this, R.string.verified_fingerprints, Toast.LENGTH_SHORT)
.show();
} }
} }
if (invite.account != null) { 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); View view = getLayoutInflater().inflate(R.layout.dialog_verify_fingerprints, null);
final CheckBox isTrustedSource = view.findViewById(R.id.trusted_source); final CheckBox isTrustedSource = view.findViewById(R.id.trusted_source);
TextView warning = view.findViewById(R.id.warning); 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.setView(view);
builder.setPositiveButton(R.string.confirm, (dialog, which) -> { builder.setPositiveButton(
R.string.confirm,
(dialog, which) -> {
if (isTrustedSource.isChecked() && invite.hasFingerprints()) { if (isTrustedSource.isChecked() && invite.hasFingerprints()) {
xmppConnectionService.verifyFingerprints(contact, invite.getFingerprints()); xmppConnectionService.verifyFingerprints(contact, invite.getFingerprints());
} }
switchToConversationDoNotAppend(contact, invite.getBody()); 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(); AlertDialog dialog = builder.create();
dialog.setCanceledOnTouchOutside(false); dialog.setCanceledOnTouchOutside(false);
dialog.setOnCancelListener(dialog1 -> StartConversationActivity.this.finish()); dialog.setOnCancelListener(dialog1 -> StartConversationActivity.this.finish());
@ -1235,7 +1399,8 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
if (account.isEnabled()) { if (account.isEnabled()) {
for (Contact contact : account.getRoster().getContacts()) { for (Contact contact : account.getRoster().getContacts()) {
Presence.Status s = contact.getShownStatus(); Presence.Status s = contact.getShownStatus();
if (contact.showInContactList() && contact.match(this, needle) if (contact.showInContactList()
&& contact.match(this, needle)
&& (!this.mHideOfflineContacts && (!this.mHideOfflineContacts
|| (needle != null && !needle.trim().isEmpty()) || (needle != null && !needle.trim().isEmpty())
|| s.compareTo(Presence.Status.OFFLINE) < 0)) { || s.compareTo(Presence.Status.OFFLINE) < 0)) {
@ -1330,7 +1495,9 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
} }
private void navigateBack() { 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 intent = new Intent(this, ConversationsActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION); intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
startActivity(intent); 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_SHOW_ENTER_JID, false);
intent.putExtra(ChooseContactActivity.EXTRA_SELECT_MULTIPLE, true); intent.putExtra(ChooseContactActivity.EXTRA_SELECT_MULTIPLE, true);
intent.putExtra(ChooseContactActivity.EXTRA_GROUP_CHAT_NAME, name.trim()); 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); intent.putExtra(ChooseContactActivity.EXTRA_TITLE_RES_ID, R.string.choose_participants);
startActivityForResult(intent, REQUEST_CREATE_CONFERENCE); startActivityForResult(intent, REQUEST_CREATE_CONFERENCE);
} }
@Override @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) { if (!xmppConnectionServiceBound) {
return; return;
} }
@ -1395,9 +1569,9 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
bookmark.setNick(nick); bookmark.setNick(nick);
} }
xmppConnectionService.createBookmark(account, bookmark); xmppConnectionService.createBookmark(account, bookmark);
final Conversation conversation = xmppConnectionService final Conversation conversation =
.findOrCreateConversation(account, conferenceJid, true, true, null, true, password); xmppConnectionService.findOrCreateConversation(
account, conferenceJid, true, true, null, true, password);
bookmark.setConversation(conversation); bookmark.setConversation(conversation);
switchToConversation(conversation); switchToConversation(conversation);
} }
@ -1417,7 +1591,6 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
} }
} }
private void setRefreshing(boolean refreshing) { private void setRefreshing(boolean refreshing) {
MyListFragment fragment = (MyListFragment) mListPagerAdapter.getItem(0); MyListFragment fragment = (MyListFragment) mListPagerAdapter.getItem(0);
if (fragment != null) { if (fragment != null) {
@ -1429,28 +1602,31 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
public void onCreatePublicChannel(Account account, String name, Jid address) { public void onCreatePublicChannel(Account account, String name, Jid address) {
mToast = Toast.makeText(this, R.string.creating_channel, Toast.LENGTH_LONG); mToast = Toast.makeText(this, R.string.creating_channel, Toast.LENGTH_LONG);
mToast.show(); mToast.show();
xmppConnectionService.createPublicChannel(account, name, address, new UiCallback<Conversation>() { xmppConnectionService.createPublicChannel(
account,
name,
address,
new UiCallback<Conversation>() {
@Override @Override
public void success(Conversation conversation) { public void success(Conversation conversation) {
runOnUiThread(() -> { runOnUiThread(
() -> {
hideToast(); hideToast();
switchToConversation(conversation); switchToConversation(conversation);
}); });
} }
@Override @Override
public void error(int errorCode, Conversation conversation) { public void error(int errorCode, Conversation conversation) {
runOnUiThread(() -> { runOnUiThread(
() -> {
replaceToast(getString(errorCode)); replaceToast(getString(errorCode));
switchToConversation(conversation); switchToConversation(conversation);
}); });
} }
@Override @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 @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) { if (mOnItemClickListener != null) {
mOnItemClickListener.onItemClick(l, v, position, id); mOnItemClickListener.onItemClick(l, v, position, id);
} }
@ -1483,7 +1660,10 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
} }
@Override @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); super.onCreateContextMenu(menu, v, menuInfo);
final StartConversationActivity activity = (StartConversationActivity) getActivity(); final StartConversationActivity activity = (StartConversationActivity) getActivity();
if (activity == null) { if (activity == null) {
@ -1517,7 +1697,9 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
if (contact.isSelf()) { if (contact.isSelf()) {
showContactDetailsItem.setVisible(false); 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(); final XmppConnection xmpp = contact.getAccount().getXmppConnection();
if (xmpp != null && xmpp.getFeatures().blocking() && !contact.isSelf()) { if (xmpp != null && xmpp.getFeatures().blocking() && !contact.isSelf()) {
if (contact.isBlocked()) { if (contact.isBlocked()) {
@ -1576,7 +1758,8 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
} }
@Override @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(); FragmentTransaction trans = fragmentManager.beginTransaction();
trans.remove(fragments[position]); trans.remove(fragments[position]);
trans.commit(); trans.commit();
@ -1626,11 +1809,13 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
if (position == 1) { if (position == 1) {
listFragment.setListAdapter(mConferenceAdapter); listFragment.setListAdapter(mConferenceAdapter);
listFragment.setContextMenu(R.menu.conference_context); listFragment.setContextMenu(R.menu.conference_context);
listFragment.setOnListItemClickListener((arg0, arg1, p, arg3) -> openConversationForBookmark(p)); listFragment.setOnListItemClickListener(
(arg0, arg1, p, arg3) -> openConversationForBookmark(p));
} else { } else {
listFragment.setListAdapter(mContactsAdapter); listFragment.setListAdapter(mContactsAdapter);
listFragment.setContextMenu(R.menu.contact_context); 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()) { if (QuickConversationsService.isQuicksy()) {
listFragment.setOnRefreshListener(StartConversationActivity.this); listFragment.setOnRefreshListener(StartConversationActivity.this);
} }
@ -1654,7 +1839,6 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
boolean forceDialog = false; boolean forceDialog = false;
Invite(final String uri) { Invite(final String uri) {
super(uri); super(uri);
} }
@ -1665,7 +1849,11 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
boolean invite() { boolean invite() {
if (!isValidJid()) { 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; return false;
} }
if (getJid() != null) { if (getJid() != null) {

View file

@ -68,9 +68,7 @@ public class UriHandlerActivity extends BaseActivity {
} }
public static void scan(final Activity activity, final boolean provisioning) { public static void scan(final Activity activity, final boolean provisioning) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M if (ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) {
|| ContextCompat.checkSelfPermission(activity, Manifest.permission.CAMERA)
== PackageManager.PERMISSION_GRANTED) {
final Intent intent = new Intent(activity, UriHandlerActivity.class); final Intent intent = new Intent(activity, UriHandlerActivity.class);
intent.setAction(UriHandlerActivity.ACTION_SCAN_QR_CODE); intent.setAction(UriHandlerActivity.ACTION_SCAN_QR_CODE);
if (provisioning) { if (provisioning) {
@ -114,6 +112,7 @@ public class UriHandlerActivity extends BaseActivity {
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
this.binding = DataBindingUtil.setContentView(this, R.layout.activity_uri_handler); this.binding = DataBindingUtil.setContentView(this, R.layout.activity_uri_handler);
Activities.setStatusAndNavigationBarColors(this, binding.getRoot());
} }
@Override @Override
@ -187,7 +186,7 @@ public class UriHandlerActivity extends BaseActivity {
startActivity(intent); startActivity(intent);
return true; return true;
} }
if (accounts.size() == 0 if (accounts.isEmpty()
&& xmppUri.isAction(XmppUri.ACTION_ROSTER) && xmppUri.isAction(XmppUri.ACTION_ROSTER)
&& "y" && "y"
.equalsIgnoreCase( .equalsIgnoreCase(
@ -203,7 +202,7 @@ public class UriHandlerActivity extends BaseActivity {
return false; return false;
} }
if (accounts.size() == 0) { if (accounts.isEmpty()) {
if (xmppUri.isValidJid()) { if (xmppUri.isValidJid()) {
intent = SignupUtils.getSignUpIntent(this); intent = SignupUtils.getSignUpIntent(this);
intent.putExtra(StartConversationActivity.EXTRA_INVITE_URI, xmppUri.toString()); intent.putExtra(StartConversationActivity.EXTRA_INVITE_URI, xmppUri.toString());
@ -259,14 +258,14 @@ public class UriHandlerActivity extends BaseActivity {
private void checkForLinkHeader(final HttpUrl url) { private void checkForLinkHeader(final HttpUrl url) {
Log.d(Config.LOGTAG, "checking for link header on " + url); Log.d(Config.LOGTAG, "checking for link header on " + url);
this.call = this.call =
HttpConnectionManager.OK_HTTP_CLIENT.newCall( HttpConnectionManager.okHttpClient(this).newCall(
new Request.Builder().url(url).head().build()); new Request.Builder().url(url).head().build());
this.call.enqueue( this.call.enqueue(
new Callback() { new Callback() {
@Override @Override
public void onFailure(@NonNull Call call, @NonNull IOException e) { public void onFailure(@NonNull Call call, @NonNull IOException e) {
Log.d(Config.LOGTAG, "unable to check HTTP url", e); Log.d(Config.LOGTAG, "unable to check HTTP url", e);
showError(R.string.no_xmpp_adddress_found); showErrorOnUiThread(R.string.no_xmpp_adddress_found);
} }
@Override @Override
@ -277,7 +276,7 @@ public class UriHandlerActivity extends BaseActivity {
return; return;
} }
} }
showError(R.string.no_xmpp_adddress_found); showErrorOnUiThread(R.string.no_xmpp_adddress_found);
} }
}); });
} }
@ -301,6 +300,10 @@ public class UriHandlerActivity extends BaseActivity {
this.binding.error.setVisibility(View.VISIBLE); this.binding.error.setVisibility(View.VISIBLE);
} }
private void showErrorOnUiThread(@StringRes int error) {
runOnUiThread(()-> showError(error));
}
private static Class<?> findShareViaAccountClass() { private static Class<?> findShareViaAccountClass() {
try { try {
return Class.forName("eu.siacs.conversations.ui.ShareViaAccountActivity"); return Class.forName("eu.siacs.conversations.ui.ShareViaAccountActivity");

View file

@ -523,13 +523,8 @@ public abstract class XmppActivity extends ActionBarActivity {
} }
protected boolean isOptimizingBattery() { protected boolean isOptimizingBattery() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { final PowerManager pm = getSystemService(PowerManager.class);
final PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE); return !pm.isIgnoringBatteryOptimizations(getPackageName());
return pm != null
&& !pm.isIgnoringBatteryOptimizations(getPackageName());
} else {
return false;
}
} }
protected boolean isAffectedByDataSaver() { protected boolean isAffectedByDataSaver() {

File diff suppressed because it is too large Load diff

View file

@ -279,8 +279,8 @@ public class ConversationAdapter
void onConversationClick(View view, Conversation conversation); void onConversationClick(View view, Conversation conversation);
} }
static class ConversationViewHolder extends RecyclerView.ViewHolder { public static class ConversationViewHolder extends RecyclerView.ViewHolder {
private final ItemConversationBinding binding; public final ItemConversationBinding binding;
private ConversationViewHolder(final ItemConversationBinding binding) { private ConversationViewHolder(final ItemConversationBinding binding) {
super(binding.getRoot()); super(binding.getRoot());

View file

@ -45,6 +45,14 @@ public class MediaAdapter extends RecyclerView.Adapter<MediaAdapter.MediaViewHol
"application/vnd.openxmlformats-officedocument.wordprocessingml.document", "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
"text/x-tex", "text/x-tex",
"text/plain"); "text/plain");
private static final List<String> ARCHIVE_MIMES =
Arrays.asList(
"application/x-7z-compressed",
"application/zip",
"application/rar",
"application/x-gtar",
"application/x-tar");
public static final List<String> CODE_MIMES = Arrays.asList("text/html", "text/xml"); public static final List<String> CODE_MIMES = Arrays.asList("text/html", "text/xml");
private final ArrayList<Attachment> attachments = new ArrayList<>(); private final ArrayList<Attachment> attachments = new ArrayList<>();
@ -95,7 +103,7 @@ public class MediaAdapter extends RecyclerView.Adapter<MediaAdapter.MediaViewHol
return R.drawable.ic_person_48dp; return R.drawable.ic_person_48dp;
} else if (mime.equals("application/vnd.android.package-archive")) { } else if (mime.equals("application/vnd.android.package-archive")) {
return R.drawable.ic_adb_48dp; return R.drawable.ic_adb_48dp;
} else if (mime.equals("application/zip") || mime.equals("application/rar")) { } else if (ARCHIVE_MIMES.contains(mime)) {
return R.drawable.ic_archive_48dp; return R.drawable.ic_archive_48dp;
} else if (mime.equals("application/epub+zip") } else if (mime.equals("application/epub+zip")
|| mime.equals("application/vnd.amazon.mobi8-ebook")) { || mime.equals("application/vnd.amazon.mobi8-ebook")) {

View file

@ -14,10 +14,13 @@ import androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts; import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.preference.ListPreference;
import androidx.preference.PreferenceFragmentCompat;
import de.monocles.chat.DownloadDefaultStickers; import de.monocles.chat.DownloadDefaultStickers;
import eu.siacs.conversations.R; import eu.siacs.conversations.R;
import eu.siacs.conversations.utils.UIHelper;
public class AttachmentsSettingsFragment extends XmppPreferenceFragment { public class AttachmentsSettingsFragment extends XmppPreferenceFragment {
@ -75,6 +78,21 @@ public class AttachmentsSettingsFragment extends XmppPreferenceFragment {
runOnUiThread(() -> Toast.makeText(requireActivity(), "Blocked media will be displayed again", Toast.LENGTH_LONG).show()); runOnUiThread(() -> Toast.makeText(requireActivity(), "Blocked media will be displayed again", Toast.LENGTH_LONG).show());
return true; return true;
}); });
final ListPreference autoAcceptFileSize = findPreference("auto_accept_file_size");
if (autoAcceptFileSize == null) {
throw new IllegalStateException("The preference resource file is missing preferences");
}
setValues(
autoAcceptFileSize,
R.array.file_size_values,
value -> {
if (value <= 0) {
return getString(R.string.never);
} else {
return UIHelper.filesizeToString(value);
}
});
} }
protected void downloadStickers() { protected void downloadStickers() {

View file

@ -196,7 +196,12 @@ public class NotificationsSettingsFragment extends XmppPreferenceFragment {
uri = appSettings().getRingtone(); uri = appSettings().getRingtone();
} }
Log.i(Config.LOGTAG, "current ringtone: " + uri); Log.i(Config.LOGTAG, "current ringtone: " + uri);
try {
this.pickRingtoneLauncher.launch(uri); this.pickRingtoneLauncher.launch(uri);
} catch (final ActivityNotFoundException e) {
Toast.makeText(requireActivity(), R.string.no_application_found, Toast.LENGTH_LONG)
.show();
}
} }
private AppSettings appSettings() { private AppSettings appSettings() {

View file

@ -4,6 +4,7 @@ import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.util.Log; import android.util.Log;
import androidx.annotation.ArrayRes;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.preference.ListPreference; import androidx.preference.ListPreference;
@ -13,6 +14,7 @@ import androidx.preference.Preference;
import com.rarepebble.colorpicker.ColorPreference; import com.rarepebble.colorpicker.ColorPreference;
import com.google.common.base.Function;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.primitives.Ints; import com.google.common.primitives.Ints;
@ -111,14 +113,29 @@ public abstract class XmppPreferenceFragment extends PreferenceFragmentCompat {
} }
} }
protected static class TimeframeSummaryProvider protected void setValues(
implements Preference.SummaryProvider<ListPreference> { final ListPreference listPreference,
@ArrayRes int resId,
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 @Nullable
@Override @Override
public CharSequence provideSummary(@NonNull ListPreference preference) { public CharSequence provideSummary(@NonNull ListPreference preference) {
final Integer value = Ints.tryParse(Strings.nullToEmpty(preference.getValue())); final Integer value =
return timeframeValueToName(preference.getContext(), value == null ? 0 : value); Ints.tryParse(Strings.nullToEmpty(preference.getValue()));
} return valueToName.apply(value == null ? 0 : value);
}
});
} }
} }

View file

@ -1,12 +1,12 @@
package eu.siacs.conversations.utils; package eu.siacs.conversations.utils;
import android.content.Context; import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.util.Log; import android.util.Log;
import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.common.base.Charsets;
import com.google.common.io.CharSink;
import com.google.common.io.Files;
import eu.siacs.conversations.AppSettings; import eu.siacs.conversations.AppSettings;
import eu.siacs.conversations.Config; import eu.siacs.conversations.Config;
@ -17,20 +17,16 @@ import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.ui.XmppActivity; import eu.siacs.conversations.ui.XmppActivity;
import java.io.BufferedReader; import java.io.File;
import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream; import java.io.OutputStream;
import java.lang.ClassNotFoundException; import java.lang.ClassNotFoundException;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale; import java.util.Locale;
public class ExceptionHelper { public class ExceptionHelper {
private static final String FILENAME = "stacktrace.txt"; private static final String FILENAME = "stacktrace.txt";
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd", Locale.ENGLISH);
public static void init(final Context context) { public static void init(final Context context) {
if (Thread.getDefaultUncaughtExceptionHandler() instanceof ExceptionHandler) { if (Thread.getDefaultUncaughtExceptionHandler() instanceof ExceptionHandler) {
@ -45,8 +41,8 @@ public class ExceptionHelper {
return false; return false;
} catch (final ClassNotFoundException e) { } } catch (final ClassNotFoundException e) { }
try { final XmppConnectionService service =
final XmppConnectionService service = activity == null ? null : activity.xmppConnectionService; activity == null ? null : activity.xmppConnectionService;
if (service == null) { if (service == null) {
return false; return false;
} }
@ -58,59 +54,52 @@ public class ExceptionHelper {
if (account == null) { if (account == null) {
return false; return false;
} }
final FileInputStream file = activity.openFileInput(FILENAME); final var file = new File(activity.getCacheDir(), FILENAME);
final InputStreamReader inputStreamReader = new InputStreamReader(file); if (!file.exists()) {
final BufferedReader stacktrace = new BufferedReader(inputStreamReader);
final StringBuilder report = new StringBuilder();
final PackageManager pm = activity.getPackageManager();
final PackageInfo packageInfo;
try {
packageInfo = pm.getPackageInfo(activity.getPackageName(), PackageManager.GET_SIGNATURES);
final String versionName = packageInfo.versionName;
final int versionCode = packageInfo.versionCode;
final int version = versionCode > 10000 ? (versionCode / 100) : versionCode;
report.append(String.format(Locale.ROOT, "Version: %s(%d)", versionName, version)).append('\n');
report.append("Last Update: ").append(DATE_FORMAT.format(new Date(packageInfo.lastUpdateTime))).append('\n');
Signature[] signatures = packageInfo.signatures;
if (signatures != null && signatures.length >= 1) {
report.append("SHA-1: ").append(CryptoHelper.getFingerprintCert(packageInfo.signatures[0].toByteArray())).append('\n');
}
report.append('\n');
} catch (final Exception e) {
return false; return false;
} }
String line; final String report;
while ((line = stacktrace.readLine()) != null) { try {
report.append(line); report = Files.asCharSource(file, Charsets.UTF_8).read();
report.append('\n'); } 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); final MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(activity);
builder.setTitle(activity.getString(R.string.crash_report_title, activity.getString(R.string.app_name))); builder.setTitle(
builder.setMessage(activity.getString(R.string.crash_report_message, activity.getString(R.string.app_name))); activity.getString(
builder.setPositiveButton(activity.getText(R.string.send_now), (dialog, which) -> { R.string.crash_report_title, activity.getString(R.string.app_name)));
builder.setMessage(
Log.d(Config.LOGTAG, "using account=" + account.getJid().asBareJid() + " to send in stack trace"); activity.getString(
Conversation conversation = service.findOrCreateConversation(account, Config.BUG_REPORTS, false, true); R.string.crash_report_message, activity.getString(R.string.app_name)));
Message message = new Message(conversation, report.toString(), Message.ENCRYPTION_NONE); 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); 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(); builder.create().show();
return true; 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 { try {
OutputStream os = context.openFileOutput(FILENAME, Context.MODE_PRIVATE); Files.asCharSink(new File(context.getCacheDir(), FILENAME), Charsets.UTF_8).write(msg);
os.write(msg.getBytes()); } catch (IOException e) {
os.flush(); Log.w(Config.LOGTAG, "could not write stack trace to file", e);
os.close();
} catch (IOException ignored) {
} }
} }
} }

View file

@ -141,6 +141,7 @@ public final class MimeUtils {
add("application/vnd.sun.xml.writer.global", "sxg"); add("application/vnd.sun.xml.writer.global", "sxg");
add("application/vnd.sun.xml.writer.template", "stw"); add("application/vnd.sun.xml.writer.template", "stw");
add("application/vnd.visio", "vsd"); add("application/vnd.visio", "vsd");
add("application/x-7z-compressed","7z");
add("application/x-abiword", "abw"); add("application/x-abiword", "abw");
add("application/x-apple-diskimage", "dmg"); add("application/x-apple-diskimage", "dmg");
add("application/x-bcpio", "bcpio"); add("application/x-bcpio", "bcpio");

View file

@ -6,24 +6,56 @@ import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.base.Throwables; import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Ordering;
import com.google.common.net.InetAddresses; import com.google.common.net.InetAddresses;
import com.google.common.primitives.Ints; import com.google.common.primitives.Ints;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.Conversations;
import eu.siacs.conversations.xmpp.Jid;
import org.minidns.dnsmessage.Question;
import org.minidns.dnsname.DnsName;
import org.minidns.dnsname.InvalidDnsNameException;
import org.minidns.dnsqueryresult.DnsQueryResult;
import org.minidns.record.A;
import org.minidns.record.AAAA;
import org.minidns.record.CNAME;
import org.minidns.record.Data;
import org.minidns.record.InternetAddressRR;
import org.minidns.record.Record;
import org.minidns.record.SRV;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Field;
import java.net.Inet4Address; import java.net.Inet4Address;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.xmpp.Jid;
//import de.gultsch.minidns.AndroidDNSClient;
import org.minidns.AbstractDnsClient; import org.minidns.AbstractDnsClient;
import org.minidns.DnsCache; import org.minidns.DnsCache;
import org.minidns.DnsClient; import org.minidns.DnsClient;
@ -44,13 +76,35 @@ import org.minidns.record.Data;
import org.minidns.record.InternetAddressRR; import org.minidns.record.InternetAddressRR;
import org.minidns.record.Record; import org.minidns.record.Record;
import org.minidns.record.SRV; import org.minidns.record.SRV;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.xmpp.Jid;
public class Resolver { public class Resolver {
private static final Comparator<Result> RESULT_COMPARATOR =
(left, right) -> {
if (left.priority == right.priority) {
if (left.directTls == right.directTls) {
if (left.ip == null && right.ip == null) {
return 0;
} else if (left.ip != null && right.ip != null) {
if (left.ip instanceof Inet4Address
&& right.ip instanceof Inet4Address) {
return 0;
} else {
return left.ip instanceof Inet4Address ? -1 : 1;
}
} else {
return left.ip != null ? -1 : 1;
}
} else {
return left.directTls ? -1 : 1;
}
} else {
return left.priority - right.priority;
}
};
private static final ExecutorService DNS_QUERY_EXECUTOR = Executors.newFixedThreadPool(12);
public static final int DEFAULT_PORT_XMPP = 5222; public static final int DEFAULT_PORT_XMPP = 5222;
private static final String DIRECT_TLS_SERVICE = "_xmpps-client"; private static final String DIRECT_TLS_SERVICE = "_xmpps-client";
@ -203,7 +257,7 @@ public class Resolver {
try { try {
DnsName.from(hostname); DnsName.from(hostname);
return false; return false;
} catch (IllegalArgumentException e) { } catch (final InvalidDnsNameException | IllegalArgumentException e) {
return true; return true;
} }
} }
@ -224,186 +278,212 @@ public class Resolver {
} }
} }
public static boolean useDirectTls(final int port) { public static boolean useDirectTls(final int port) {
return port == 443 || port == 5223; return port == 443 || port == 5223;
} }
public static List<Result> resolve(final String domain) { public static List<Result> resolve(final String domain) {
final List<Result> ipResults = fromIpAddress(domain); final List<Result> ipResults = fromIpAddress(domain);
if (ipResults.size() > 0) { if (!ipResults.isEmpty()) {
return ipResults; return ipResults;
} }
final List<Result> results = new ArrayList<>();
final List<Result> fallbackResults = new ArrayList<>(); final var startTls = resolveSrvAsFuture(domain, false);
final Thread[] threads = new Thread[3]; final var directTls = resolveSrvAsFuture(domain, true);
threads[0] = new Thread(() -> {
try { final var combined = merge(ImmutableList.of(startTls, directTls));
final List<Result> list = resolveSrv(domain, true);
synchronized (results) { final var combinedWithFallback =
results.addAll(list); Futures.transformAsync(
} combined,
} catch (final Throwable throwable) { results -> {
if (!(Throwables.getRootCause(throwable) instanceof InterruptedException)) { if (results.isEmpty()) {
Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": error resolving SRV record (direct TLS)", throwable); return resolveNoSrvAsFuture(DnsName.from(domain), true);
}
}
});
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;
}
} else { } else {
threads[2].join(); return Futures.immediateFuture(results);
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();
} }
},
MoreExecutors.directExecutor());
final var orderedFuture =
Futures.transform(
combinedWithFallback,
all -> Ordering.from(RESULT_COMPARATOR).immutableSortedCopy(all),
MoreExecutors.directExecutor());
try { try {
Result result = new Result(); final var ordered = orderedFuture.get();
result.ip = InetAddress.getByName(domain); Log.d(Config.LOGTAG, "Resolver (" + ordered.size() + "): " + ordered);
result.port = DEFAULT_PORT_XMPP; return ordered;
result.authenticated = true; } catch (final ExecutionException e) {
return Collections.singletonList(result); Log.d(Config.LOGTAG, "error resolving DNS", e);
} catch (UnknownHostException e) { return Collections.emptyList();
} catch (final InterruptedException e) {
Log.d(Config.LOGTAG, "DNS resolution interrupted");
return Collections.emptyList(); return Collections.emptyList();
} }
} }
private static List<Result> resolveSrv(String domain, final boolean directTls) throws IOException { private static List<Result> fromIpAddress(final String domain) {
final String dnsNameS = (directTls ? DIRECT_TLS_SERVICE : STARTTLS_SERVICE) + "._tcp." + domain; if (IP.matches(domain)) {
DnsName dnsName = DnsName.from(dnsNameS); final InetAddress inetAddress;
ResolverResult<SRV> result = resolveWithFallback(dnsName, SRV.class); try {
final List<Result> results = new ArrayList<>(); inetAddress = InetAddress.getByName(domain);
final List<Thread> threads = new ArrayList<>(); } catch (final UnknownHostException e) {
for (SRV record : result.getAnswersOrEmptySet()) { return Collections.emptyList();
if (record.name.length() == 0 && record.priority == 0) { }
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; continue;
} }
final boolean authentic = result.isAuthenticData() || record.target.toString().equals(knownSRV.get(dnsNameS)); final var ipv4sRaw =
threads.add(new Thread(() -> { resolveIpsAsFuture(
final List<Result> ipv4s = resolveIp(record, A.class, authentic, directTls); record, A.class, srvResolverResult.isAuthenticData(), directTls);
if (ipv4s.size() == 0) { final var ipv4s =
Result resolverResult = Result.fromRecord(record, directTls); Futures.transform(
resolverResult.authenticated = result.isAuthenticData(); ipv4sRaw,
ipv4s.add(resolverResult); results -> {
} if (results.isEmpty()) {
synchronized (results) { final Result resolverResult =
results.addAll(ipv4s); Result.fromRecord(record, directTls);
} resolverResult.authenticated =
srvResolverResult.isAuthenticData();
})); return Collections.singletonList(resolverResult);
threads.add(new Thread(() -> { } else {
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();
}
}
return results; 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) { private static ListenableFuture<List<Result>> merge(
List<Result> list = new ArrayList<>(); final Collection<ListenableFuture<List<Result>>> futures) {
try { return Futures.transform(
ResolverResult<D> results = resolveWithFallback(srv.target, type); Futures.successfulAsList(futures),
for (D record : results.getAnswersOrEmptySet()) { 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); 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(); resolverResult.ip = record.getInetAddress();
list.add(resolverResult); builder.add(resolverResult);
} }
} catch (Throwable t) { return builder.build();
Log.d(Config.LOGTAG, Resolver.class.getSimpleName() + ": error resolving " + type.getSimpleName() + " " + t.getMessage()); },
} MoreExecutors.directExecutor());
return list;
} }
private static List<Result> resolveNoSrvRecords(DnsName dnsName, boolean withCnames) { private static ListenableFuture<List<Result>> resolveNoSrvAsFuture(
final List<Result> results = new ArrayList<>(); final DnsName dnsName, boolean cName) {
try { final ImmutableList.Builder<ListenableFuture<List<Result>>> futuresBuilder =
ResolverResult<A> aResult = resolveWithFallback(dnsName, A.class); new ImmutableList.Builder<>();
for (A a : aResult.getAnswersOrEmptySet()) { ListenableFuture<List<Result>> aRecordResults =
Result r = Result.createDefault(dnsName, a.getInetAddress()); Futures.transform(
r.authenticated = aResult.isAuthenticData(); resolveAsFuture(dnsName, A.class),
results.add(r); 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); final ImmutableList<ListenableFuture<List<Result>>> futures = futuresBuilder.build();
for (AAAA aaaa : aaaaResult.getAnswersOrEmptySet()) { final var noSrvFallbacks = merge(futures);
Result r = Result.createDefault(dnsName, aaaa.getInetAddress()); return Futures.transform(
r.authenticated = aaaaResult.isAuthenticData(); noSrvFallbacks,
results.add(r); results -> {
} if (results.isEmpty()) {
if (results.size() == 0 && withCnames) { return Collections.singletonList(Result.createDefault(dnsName));
ResolverResult<CNAME> cnameResult = resolveWithFallback(dnsName, CNAME.class); } else {
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));
return results; 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)); final Question question = new Question(dnsName, Record.TYPE.getType(type));
if (!DNSSECLESS_TLDS.contains(dnsName.getLabels()[0].toString())) { if (!DNSSECLESS_TLDS.contains(dnsName.getLabels()[0].toString())) {
try { try {
@ -421,9 +501,11 @@ public class Resolver {
} }
} }
return ResolverApi.INSTANCE.resolve(question); 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 DOMAIN = "domain";
public static final String IP = "ip"; public static final String IP = "ip";
public static final String HOSTNAME = "hostname"; public static final String HOSTNAME = "hostname";
@ -438,40 +520,42 @@ public class Resolver {
private boolean authenticated = false; private boolean authenticated = false;
private int priority; private int priority;
static Result fromRecord(SRV srv, boolean directTls) { static Result fromRecord(final SRV srv, final boolean directTls) {
Result result = new Result(); final Result result = new Result();
result.port = srv.port; result.port = srv.port;
result.hostname = srv.name; result.hostname = srv.target;
result.directTls = directTls; result.directTls = directTls;
result.priority = srv.priority; result.priority = srv.priority;
return result; return result;
} }
static Result createDefault(DnsName hostname, InetAddress ip) { static Result createDefault(final DnsName hostname, final InetAddress ip, final boolean authenticated) {
Result result = new Result(); Result result = new Result();
result.port = DEFAULT_PORT_XMPP; result.port = DEFAULT_PORT_XMPP;
result.hostname = hostname; result.hostname = hostname;
result.ip = ip; result.ip = ip;
result.authenticated = authenticated;
return result; return result;
} }
static Result createDefault(DnsName hostname) { static Result createDefault(final DnsName hostname) {
return createDefault(hostname, null); return createDefault(hostname, null, false);
} }
public static Result fromCursor(Cursor cursor) { public static Result fromCursor(final Cursor cursor) {
final Result result = new Result(); final Result result = new Result();
try { try {
result.ip = InetAddress.getByAddress(cursor.getBlob(cursor.getColumnIndex(IP))); result.ip =
} catch (UnknownHostException e) { InetAddress.getByAddress(cursor.getBlob(cursor.getColumnIndexOrThrow(IP)));
} catch (final UnknownHostException e) {
result.ip = null; result.ip = null;
} }
final String hostname = cursor.getString(cursor.getColumnIndex(HOSTNAME)); final String hostname = cursor.getString(cursor.getColumnIndexOrThrow(HOSTNAME));
result.hostname = hostname == null ? null : DnsName.from(hostname); result.hostname = hostname == null ? null : DnsName.from(hostname);
result.port = cursor.getInt(cursor.getColumnIndex(PORT)); result.port = cursor.getInt(cursor.getColumnIndexOrThrow(PORT));
result.priority = cursor.getInt(cursor.getColumnIndex(PRIORITY)); result.priority = cursor.getInt(cursor.getColumnIndexOrThrow(PRIORITY));
result.authenticated = cursor.getInt(cursor.getColumnIndex(AUTHENTICATED)) > 0; result.authenticated = cursor.getInt(cursor.getColumnIndexOrThrow(AUTHENTICATED)) > 0;
result.directTls = cursor.getInt(cursor.getColumnIndex(DIRECT_TLS)) > 0; result.directTls = cursor.getInt(cursor.getColumnIndexOrThrow(DIRECT_TLS)) > 0;
return result; return result;
} }
@ -479,26 +563,18 @@ public class Resolver {
public boolean equals(Object o) { public boolean equals(Object o) {
if (this == o) return true; if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false; if (o == null || getClass() != o.getClass()) return false;
Result result = (Result) o; Result result = (Result) o;
return port == result.port
if (port != result.port) return false; && directTls == result.directTls
if (directTls != result.directTls) return false; && authenticated == result.authenticated
if (authenticated != result.authenticated) return false; && priority == result.priority
if (priority != result.priority) return false; && Objects.equal(ip, result.ip)
if (ip != null ? !ip.equals(result.ip) : result.ip != null) return false; && Objects.equal(hostname, result.hostname);
return hostname != null ? hostname.equals(result.hostname) : result.hostname == null;
} }
@Override @Override
public int hashCode() { public int hashCode() {
int result = ip != null ? ip.hashCode() : 0; return Objects.hashCode(ip, hostname, port, directTls, authenticated, priority);
result = 31 * result + (hostname != null ? hostname.hashCode() : 0);
result = 31 * result + port;
result = 31 * result + (directTls ? 1 : 0);
result = 31 * result + (authenticated ? 1 : 0);
result = 31 * result + priority;
return result;
} }
public InetAddress getIp() { public InetAddress getIp() {
@ -522,38 +598,16 @@ public class Resolver {
} }
@Override @Override
@NonNull
public String toString() { public String toString() {
return "Result{" + return MoreObjects.toStringHelper(this)
"ip='" + (ip == null ? null : ip.getHostAddress()) + '\'' + .add("ip", ip)
", hostame='" + (hostname == null ? null : hostname.toString()) + '\'' + .add("hostname", hostname)
", port=" + port + .add("port", port)
", directTls=" + directTls + .add("directTls", directTls)
", authenticated=" + authenticated + .add("authenticated", authenticated)
", priority=" + priority + .add("priority", priority)
'}'; .toString();
}
@Override
public int compareTo(@NonNull Result result) {
if (result.priority == priority) {
if (directTls == result.directTls) {
if (ip == null && result.ip == null) {
return 0;
} else if (ip != null && result.ip != null) {
if (ip instanceof Inet4Address && result.ip instanceof Inet4Address) {
return 0;
} else {
return ip instanceof Inet4Address ? -1 : 1;
}
} else {
return ip != null ? -1 : 1;
}
} else {
return directTls ? 1 : -1;
}
} else {
return priority - result.priority;
}
} }
public ContentValues toContentValues() { public ContentValues toContentValues() {
@ -626,5 +680,4 @@ public class Resolver {
return result; return result;
} }
} }
} }

View file

@ -18,6 +18,7 @@ import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationCompat;
import androidx.work.ForegroundInfo; import androidx.work.ForegroundInfo;
import androidx.work.WorkManager;
import androidx.work.Worker; import androidx.work.Worker;
import androidx.work.WorkerParameters; import androidx.work.WorkerParameters;
@ -35,7 +36,6 @@ import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.persistance.DatabaseBackend; import eu.siacs.conversations.persistance.DatabaseBackend;
import eu.siacs.conversations.persistance.FileBackend; import eu.siacs.conversations.persistance.FileBackend;
import eu.siacs.conversations.receiver.WorkManagerEventReceiver;
import eu.siacs.conversations.utils.BackupFileHeader; import eu.siacs.conversations.utils.BackupFileHeader;
import eu.siacs.conversations.utils.Compatibility; import eu.siacs.conversations.utils.Compatibility;
@ -99,6 +99,7 @@ public class ExportBackupWorker extends Worker {
@NonNull @NonNull
@Override @Override
public Result doWork() { public Result doWork() {
setForegroundAsync(getForegroundInfo());
final List<File> files; final List<File> files;
try { try {
files = export(); files = export();
@ -227,18 +228,14 @@ public class ExportBackupWorker extends Worker {
IV, IV,
salt); salt);
final var notification = getNotification(); final var notification = getNotification();
if (!recurringBackup) {
final var cancel = new Intent(context, WorkManagerEventReceiver.class);
cancel.setAction(WorkManagerEventReceiver.ACTION_STOP_BACKUP);
final var cancelPendingIntent = final var cancelPendingIntent =
PendingIntent.getBroadcast(context, 197, cancel, PENDING_INTENT_FLAGS); WorkManager.getInstance(context).createCancelPendingIntent(getId());
notification.addAction( notification.addAction(
new NotificationCompat.Action.Builder( new NotificationCompat.Action.Builder(
R.drawable.ic_cancel_24dp, R.drawable.ic_cancel_24dp,
context.getString(R.string.cancel), context.getString(R.string.cancel),
cancelPendingIntent) cancelPendingIntent)
.build()); .build());
}
final Progress progress = new Progress(notification, max, count); final Progress progress = new Progress(notification, max, count);
final File directory = file.getParentFile(); final File directory = file.getParentFile();
if (directory != null && directory.mkdirs()) { if (directory != null && directory.mkdirs()) {

View file

@ -5,7 +5,9 @@ import androidx.annotation.NonNull;
import com.google.common.base.Optional; import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.google.common.base.Strings;
import com.google.common.primitives.Ints; import com.google.common.primitives.Ints;
import com.google.common.primitives.Longs;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
@ -16,7 +18,7 @@ import java.util.stream.Collectors;
import eu.siacs.conversations.utils.XmlHelper; import eu.siacs.conversations.utils.XmlHelper;
import eu.siacs.conversations.xmpp.InvalidJid; import eu.siacs.conversations.xmpp.InvalidJid;
import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.Jid;
import eu.siacs.conversations.xmpp.stanzas.MessagePacket; import im.conversations.android.xmpp.model.stanza.Message;
public class Element implements Node { public class Element implements Node {
private final String name; private final String name;
@ -141,6 +143,10 @@ public class Element implements Node {
return ImmutableList.copyOf(this.children); return ImmutableList.copyOf(this.children);
} }
public void setAttribute(final String name, final boolean value) {
this.setAttribute(name, value ? "1" : "0");
}
// Deprecated: you probably want bindTo or replaceChildren // Deprecated: you probably want bindTo or replaceChildren
public Element setChildren(List<Element> children) { public Element setChildren(List<Element> children) {
this.childNodes = new ArrayList(children); this.childNodes = new ArrayList(children);
@ -165,6 +171,31 @@ public class Element implements Node {
return this.childNodes.stream().map(Node::getContent).filter(c -> c != null).collect(Collectors.joining()); return this.childNodes.stream().map(Node::getContent).filter(c -> c != null).collect(Collectors.joining());
} }
public long getLongAttribute(final String name) {
final var value = Longs.tryParse(Strings.nullToEmpty(this.attributes.get(name)));
return value == null ? 0 : value;
}
public Optional<Integer> getOptionalIntAttribute(final String name) {
final String value = getAttribute(name);
if (value == null) {
return Optional.absent();
}
return Optional.fromNullable(Ints.tryParse(value));
}
public Jid getAttributeAsJid(String name) {
final String jid = this.getAttribute(name);
if (jid != null && !jid.isEmpty()) {
try {
return Jid.ofEscaped(jid);
} catch (final IllegalArgumentException e) {
return InvalidJid.of(jid, this instanceof Message);
}
}
return null;
}
public Element setAttribute(String name, String value) { public Element setAttribute(String name, String value) {
if (name != null && value != null) { if (name != null && value != null) {
this.attributes.put(name, value); this.attributes.put(name, value);
@ -224,7 +255,7 @@ public class Element implements Node {
return result; return result;
} }
public Element removeAttribute(String name) { public Element removeAttribute(final String name) {
this.attributes.remove(name); this.attributes.remove(name);
return this; return this;
} }
@ -242,26 +273,6 @@ public class Element implements Node {
} }
} }
public Optional<Integer> getOptionalIntAttribute(final String name) {
final String value = getAttribute(name);
if (value == null) {
return Optional.absent();
}
return Optional.fromNullable(Ints.tryParse(value));
}
public Jid getAttributeAsJid(String name) {
final String jid = this.getAttribute(name);
if (jid != null && !jid.isEmpty()) {
try {
return Jid.ofEscaped(jid);
} catch (final IllegalArgumentException e) {
return InvalidJid.of(jid, this instanceof MessagePacket);
}
}
return null;
}
public Hashtable<String, String> getAttributes() { public Hashtable<String, String> getAttributes() {
return this.attributes; return this.attributes;
} }

View file

@ -37,7 +37,7 @@ public class LocalizedContent {
} }
} }
} }
if (contents.size() == 0) { if (contents.isEmpty()) {
return null; return null;
} }
final String userLanguage = Locale.getDefault().getLanguage(); final String userLanguage = Locale.getDefault().getLanguage();

View file

@ -1,8 +1,29 @@
package eu.siacs.conversations.xml; package eu.siacs.conversations.xml;
public final class Namespace { public final class Namespace {
public static final String ADDRESSING = "http://jabber.org/protocol/address";
public static final String AXOLOTL = "eu.siacs.conversations.axolotl";
public static final String PGP_SIGNED = "jabber:x:signed";
public static final String PGP_ENCRYPTED = "jabber:x:encrypted";
public static final String AXOLOTL_BUNDLES = AXOLOTL + ".bundles";
public static final String AXOLOTL_DEVICE_LIST = AXOLOTL + ".devicelist";
public static final String HINTS = "urn:xmpp:hints";
public static final String MESSAGE_ARCHIVE_MANAGEMENT = "urn:xmpp:mam:2";
public static final String VERSION = "jabber:iq:version";
public static final String LAST_MESSAGE_CORRECTION = "urn:xmpp:message-correct:0";
public static final String RESULT_SET_MANAGEMENT = "http://jabber.org/protocol/rsm";
public static final String CHAT_MARKERS = "urn:xmpp:chat-markers:0";
public static final String CHAT_STATES = "http://jabber.org/protocol/chatstates";
public static final String DELIVERY_RECEIPTS = "urn:xmpp:receipts";
public static final String REACTIONS = "urn:xmpp:reactions:0";
public static final String VCARD_TEMP = "vcard-temp";
public static final String VCARD_TEMP_UPDATE = "vcard-temp:x:update";
public static final String DELAY = "urn:xmpp:delay";
public static final String OCCUPANT_ID = "urn:xmpp:occupant-id:0";
public static final String STREAMS = "http://etherx.jabber.org/streams"; public static final String STREAMS = "http://etherx.jabber.org/streams";
public static final String STANZAS = "urn:ietf:params:xml:ns:xmpp-stanzas";
public static final String JABBER_CLIENT = "jabber:client"; public static final String JABBER_CLIENT = "jabber:client";
public static final String FORWARD = "urn:xmpp:forward:0";
public static final String DISCO_ITEMS = "http://jabber.org/protocol/disco#items"; public static final String DISCO_ITEMS = "http://jabber.org/protocol/disco#items";
public static final String DISCO_INFO = "http://jabber.org/protocol/disco#info"; public static final String DISCO_INFO = "http://jabber.org/protocol/disco#info";
public static final String EXTERNAL_SERVICE_DISCOVERY = "urn:xmpp:extdisco:2"; public static final String EXTERNAL_SERVICE_DISCOVERY = "urn:xmpp:extdisco:2";
@ -23,12 +44,15 @@ public final class Namespace {
public static final String FAST = "urn:xmpp:fast:0"; public static final String FAST = "urn:xmpp:fast:0";
public static final String TLS = "urn:ietf:params:xml:ns:xmpp-tls"; public static final String TLS = "urn:ietf:params:xml:ns:xmpp-tls";
public static final String PUBSUB = "http://jabber.org/protocol/pubsub"; public static final String PUBSUB = "http://jabber.org/protocol/pubsub";
public static final String PUBSUB_EVENT = PUBSUB + "#event";
public static final String MUC = "http://jabber.org/protocol/muc";
public static final String PUBSUB_PUBLISH_OPTIONS = PUBSUB + "#publish-options"; public static final String PUBSUB_PUBLISH_OPTIONS = PUBSUB + "#publish-options";
public static final String PUBSUB_CONFIG_NODE_MAX = PUBSUB + "#config-node-max"; public static final String PUBSUB_CONFIG_NODE_MAX = PUBSUB + "#config-node-max";
public static final String PUBSUB_ERROR = PUBSUB + "#errors"; public static final String PUBSUB_ERROR = PUBSUB + "#errors";
public static final String PUBSUB_OWNER = PUBSUB + "#owner"; public static final String PUBSUB_OWNER = PUBSUB + "#owner";
public static final String NICK = "http://jabber.org/protocol/nick"; public static final String NICK = "http://jabber.org/protocol/nick";
public static final String FLEXIBLE_OFFLINE_MESSAGE_RETRIEVAL = "http://jabber.org/protocol/offline"; public static final String FLEXIBLE_OFFLINE_MESSAGE_RETRIEVAL =
"http://jabber.org/protocol/offline";
public static final String BIND = "urn:ietf:params:xml:ns:xmpp-bind"; public static final String BIND = "urn:ietf:params:xml:ns:xmpp-bind";
public static final String BIND2 = "urn:xmpp:bind:0"; public static final String BIND2 = "urn:xmpp:bind:0";
public static final String STREAM_MANAGEMENT = "urn:xmpp:sm:3"; public static final String STREAM_MANAGEMENT = "urn:xmpp:sm:3";
@ -48,7 +72,8 @@ public final class Namespace {
public static final String JINGLE_TRANSPORTS_S5B = "urn:xmpp:jingle:transports:s5b:1"; public static final String JINGLE_TRANSPORTS_S5B = "urn:xmpp:jingle:transports:s5b:1";
public static final String JINGLE_TRANSPORTS_IBB = "urn:xmpp:jingle:transports:ibb:1"; public static final String JINGLE_TRANSPORTS_IBB = "urn:xmpp:jingle:transports:ibb:1";
public static final String JINGLE_TRANSPORT_ICE_UDP = "urn:xmpp:jingle:transports:ice-udp:1"; public static final String JINGLE_TRANSPORT_ICE_UDP = "urn:xmpp:jingle:transports:ice-udp:1";
public static final String JINGLE_TRANSPORT_WEBRTC_DATA_CHANNEL = "urn:xmpp:jingle:transports:webrtc-datachannel:1"; public static final String JINGLE_TRANSPORT_WEBRTC_DATA_CHANNEL =
"urn:xmpp:jingle:transports:webrtc-datachannel:1";
public static final String JINGLE_TRANSPORT = "urn:xmpp:jingle:transports:dtls-sctp:1"; public static final String JINGLE_TRANSPORT = "urn:xmpp:jingle:transports:dtls-sctp:1";
public static final String JINGLE_APPS_RTP = "urn:xmpp:jingle:apps:rtp:1"; public static final String JINGLE_APPS_RTP = "urn:xmpp:jingle:apps:rtp:1";
@ -57,9 +82,12 @@ public final class Namespace {
public static final String JINGLE_APPS_GROUPING = "urn:xmpp:jingle:apps:grouping:0"; public static final String JINGLE_APPS_GROUPING = "urn:xmpp:jingle:apps:grouping:0";
public static final String JINGLE_FEATURE_AUDIO = "urn:xmpp:jingle:apps:rtp:audio"; public static final String JINGLE_FEATURE_AUDIO = "urn:xmpp:jingle:apps:rtp:audio";
public static final String JINGLE_FEATURE_VIDEO = "urn:xmpp:jingle:apps:rtp:video"; public static final String JINGLE_FEATURE_VIDEO = "urn:xmpp:jingle:apps:rtp:video";
public static final String JINGLE_RTP_HEADER_EXTENSIONS = "urn:xmpp:jingle:apps:rtp:rtp-hdrext:0"; public static final String JINGLE_RTP_HEADER_EXTENSIONS =
public static final String JINGLE_RTP_FEEDBACK_NEGOTIATION = "urn:xmpp:jingle:apps:rtp:rtcp-fb:0"; "urn:xmpp:jingle:apps:rtp:rtp-hdrext:0";
public static final String JINGLE_RTP_SOURCE_SPECIFIC_MEDIA_ATTRIBUTES = "urn:xmpp:jingle:apps:rtp:ssma:0"; public static final String JINGLE_RTP_FEEDBACK_NEGOTIATION =
"urn:xmpp:jingle:apps:rtp:rtcp-fb:0";
public static final String JINGLE_RTP_SOURCE_SPECIFIC_MEDIA_ATTRIBUTES =
"urn:xmpp:jingle:apps:rtp:ssma:0";
public static final String IBB = "http://jabber.org/protocol/ibb"; public static final String IBB = "http://jabber.org/protocol/ibb";
public static final String PING = "urn:xmpp:ping"; public static final String PING = "urn:xmpp:ping";
public static final String PUSH = "urn:xmpp:push:0"; public static final String PUSH = "urn:xmpp:push:0";
@ -70,8 +98,10 @@ public final class Namespace {
public static final String INVITE = "urn:xmpp:invite"; public static final String INVITE = "urn:xmpp:invite";
public static final String PARS = "urn:xmpp:pars:0"; public static final String PARS = "urn:xmpp:pars:0";
public static final String EASY_ONBOARDING_INVITE = "urn:xmpp:invite#invite"; public static final String EASY_ONBOARDING_INVITE = "urn:xmpp:invite#invite";
public static final String OMEMO_DTLS_SRTP_VERIFICATION = "http://gultsch.de/xmpp/drafts/omemo/dlts-srtp-verification"; public static final String OMEMO_DTLS_SRTP_VERIFICATION =
public static final String JINGLE_TRANSPORT_ICE_OPTION = "http://gultsch.de/xmpp/drafts/jingle/transports/ice-udp/option"; "http://gultsch.de/xmpp/drafts/omemo/dlts-srtp-verification";
public static final String JINGLE_TRANSPORT_ICE_OPTION =
"http://gultsch.de/xmpp/drafts/jingle/transports/ice-udp/option";
public static final String UNIFIED_PUSH = "http://gultsch.de/xmpp/drafts/unified-push"; public static final String UNIFIED_PUSH = "http://gultsch.de/xmpp/drafts/unified-push";
public static final String VCARD4 = "urn:ietf:params:xml:ns:vcard-4.0"; public static final String VCARD4 = "urn:ietf:params:xml:ns:vcard-4.0";
public static final String REPORTING = "urn:xmpp:reporting:1"; public static final String REPORTING = "urn:xmpp:reporting:1";
@ -80,4 +110,7 @@ public final class Namespace {
public static final String HASHES = "urn:xmpp:hashes:2"; public static final String HASHES = "urn:xmpp:hashes:2";
public static final String MDS_DISPLAYED = "urn:xmpp:mds:displayed:0"; public static final String MDS_DISPLAYED = "urn:xmpp:mds:displayed:0";
public static final String MDS_SERVER_ASSIST = "urn:xmpp:mds:server-assist:0"; public static final String MDS_SERVER_ASSIST = "urn:xmpp:mds:server-assist:0";
public static final String ENTITY_CAPABILITIES = "http://jabber.org/protocol/caps";
public static final String ENTITY_CAPABILITIES_2 = "urn:xmpp:caps";
} }

View file

@ -3,6 +3,12 @@ package eu.siacs.conversations.xml;
import android.util.Log; import android.util.Log;
import android.util.Xml; import android.util.Xml;
import eu.siacs.conversations.Config;
import im.conversations.android.xmpp.ExtensionFactory;
import im.conversations.android.xmpp.model.Extension;
import im.conversations.android.xmpp.model.StreamElement;
import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserException;
@ -11,8 +17,6 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import eu.siacs.conversations.Config;
public class XmlReader implements Closeable { public class XmlReader implements Closeable {
private final XmlPullParser parser; private final XmlPullParser parser;
private InputStream is; private InputStream is;
@ -90,8 +94,21 @@ public class XmlReader implements Closeable {
return null; return null;
} }
public Element readElement(Tag currentTag) throws IOException { public <T extends StreamElement> T readElement(final Tag current, final Class<T> clazz)
Element element = new Element(currentTag.getName()); throws IOException {
final Element element = readElement(current);
if (clazz.isInstance(element)) {
return clazz.cast(element);
}
throw new IOException(
String.format("Read unexpected {%s}%s", element.getNamespace(), element.getName()));
}
public Element readElement(final Tag currentTag) throws IOException {
final var attributes = currentTag.getAttributes();
final var namespace = attributes.get("xmlns");
final var name = currentTag.getName();
final Element element = ExtensionFactory.create(name, namespace);
element.setAttributes(currentTag.getAttributes()); element.setAttributes(currentTag.getAttributes());
Tag nextTag = this.readTag(); Tag nextTag = this.readTag();
if (nextTag == null) { if (nextTag == null) {

File diff suppressed because it is too large Load diff

View file

@ -59,8 +59,8 @@ public class Data extends Element {
field.setValues(values); field.setValues(values);
} }
public void submit(Bundle options) { public void submit(final Bundle options) {
for (Field field : getFields()) { for (final Field field : getFields()) {
if (options.containsKey(field.getFieldName())) { if (options.containsKey(field.getFieldName())) {
field.setValue(options.getString(field.getFieldName())); field.setValue(options.getString(field.getFieldName()));
} }

View file

@ -34,14 +34,13 @@ import eu.siacs.conversations.xmpp.Jid;
import eu.siacs.conversations.xmpp.XmppConnection; import eu.siacs.conversations.xmpp.XmppConnection;
import eu.siacs.conversations.xmpp.jingle.stanzas.Content; import eu.siacs.conversations.xmpp.jingle.stanzas.Content;
import eu.siacs.conversations.xmpp.jingle.stanzas.GenericDescription; import eu.siacs.conversations.xmpp.jingle.stanzas.GenericDescription;
import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket;
import eu.siacs.conversations.xmpp.jingle.stanzas.Propose; import eu.siacs.conversations.xmpp.jingle.stanzas.Propose;
import eu.siacs.conversations.xmpp.jingle.stanzas.Reason; import eu.siacs.conversations.xmpp.jingle.stanzas.Reason;
import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription; import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription;
import eu.siacs.conversations.xmpp.jingle.transports.InbandBytestreamsTransport; import eu.siacs.conversations.xmpp.jingle.transports.InbandBytestreamsTransport;
import eu.siacs.conversations.xmpp.jingle.transports.Transport; import eu.siacs.conversations.xmpp.jingle.transports.Transport;
import eu.siacs.conversations.xmpp.stanzas.IqPacket; import im.conversations.android.xmpp.model.jingle.Jingle;
import eu.siacs.conversations.xmpp.stanzas.MessagePacket; import im.conversations.android.xmpp.model.stanza.Iq;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.security.SecureRandom; import java.security.SecureRandom;
@ -77,9 +76,11 @@ public class JingleConnectionManager extends AbstractConnectionManager {
return Base64.encodeToString(id, Base64.NO_WRAP | Base64.NO_PADDING | Base64.URL_SAFE); return Base64.encodeToString(id, Base64.NO_WRAP | Base64.NO_PADDING | Base64.URL_SAFE);
} }
public void deliverPacket(final Account account, final JinglePacket packet) { public void deliverPacket(final Account account, final Iq packet) {
final String sessionId = packet.getSessionId(); final var jingle = packet.getExtension(Jingle.class);
final JinglePacket.Action action = packet.getAction(); Preconditions.checkNotNull(jingle,"Passed iq packet w/o jingle extension to Connection Manager");
final String sessionId = jingle.getSessionId();
final Jingle.Action action = jingle.getAction();
if (sessionId == null) { if (sessionId == null) {
respondWithJingleError(account, packet, "unknown-session", "item-not-found", "cancel"); respondWithJingleError(account, packet, "unknown-session", "item-not-found", "cancel");
return; return;
@ -88,13 +89,13 @@ public class JingleConnectionManager extends AbstractConnectionManager {
respondWithJingleError(account, packet, null, "bad-request", "cancel"); respondWithJingleError(account, packet, null, "bad-request", "cancel");
return; return;
} }
final AbstractJingleConnection.Id id = AbstractJingleConnection.Id.of(account, packet); final AbstractJingleConnection.Id id = AbstractJingleConnection.Id.of(account, packet, jingle);
final AbstractJingleConnection existingJingleConnection = connections.get(id); final AbstractJingleConnection existingJingleConnection = connections.get(id);
if (existingJingleConnection != null) { if (existingJingleConnection != null) {
existingJingleConnection.deliverPacket(packet); existingJingleConnection.deliverPacket(packet);
} else if (action == JinglePacket.Action.SESSION_INITIATE) { } else if (action == Jingle.Action.SESSION_INITIATE) {
final Jid from = packet.getFrom(); final Jid from = packet.getFrom();
final Content content = packet.getJingleContent(); final Content content = jingle.getJingleContent();
final String descriptionNamespace = final String descriptionNamespace =
content == null ? null : content.getDescriptionNamespace(); content == null ? null : content.getDescriptionNamespace();
final AbstractJingleConnection connection; final AbstractJingleConnection connection;
@ -165,14 +166,14 @@ public class JingleConnectionManager extends AbstractConnectionManager {
} }
private void sendSessionTerminate( private void sendSessionTerminate(
final Account account, final IqPacket request, final AbstractJingleConnection.Id id) { final Account account, final Iq request, final AbstractJingleConnection.Id id) {
mXmppConnectionService.sendIqPacket( mXmppConnectionService.sendIqPacket(
account, request.generateResponse(IqPacket.TYPE.RESULT), null); account, request.generateResponse(Iq.Type.RESULT), null);
final JinglePacket sessionTermination = final var iq = new Iq(Iq.Type.SET);
new JinglePacket(JinglePacket.Action.SESSION_TERMINATE, id.sessionId); iq.setTo(id.with);
sessionTermination.setTo(id.with); final var sessionTermination = iq.addExtension(new Jingle(Jingle.Action.SESSION_TERMINATE, id.sessionId));
sessionTermination.setReason(Reason.BUSY, null); sessionTermination.setReason(Reason.BUSY, null);
mXmppConnectionService.sendIqPacket(account, sessionTermination, null); mXmppConnectionService.sendIqPacket(account, iq, null);
} }
private boolean isUsingClearNet(final Account account) { private boolean isUsingClearNet(final Account account) {
@ -265,11 +266,11 @@ public class JingleConnectionManager extends AbstractConnectionManager {
void respondWithJingleError( void respondWithJingleError(
final Account account, final Account account,
final IqPacket original, final Iq original,
final String jingleCondition, final String jingleCondition,
final String condition, final String condition,
final String conditionType) { final String conditionType) {
final IqPacket response = original.generateResponse(IqPacket.TYPE.ERROR); final Iq response = original.generateResponse(Iq.Type.ERROR);
final Element error = response.addChild("error"); final Element error = response.addChild("error");
error.setAttribute("type", conditionType); error.setAttribute("type", conditionType);
error.addChild(condition, "urn:ietf:params:xml:ns:xmpp-stanzas"); error.addChild(condition, "urn:ietf:params:xml:ns:xmpp-stanzas");
@ -440,7 +441,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
final int activeDevices = account.activeDevicesWithRtpCapability(); final int activeDevices = account.activeDevicesWithRtpCapability();
Log.d(Config.LOGTAG, "active devices with rtp capability: " + activeDevices); Log.d(Config.LOGTAG, "active devices with rtp capability: " + activeDevices);
if (activeDevices == 0) { if (activeDevices == 0) {
final MessagePacket reject = final var reject =
mXmppConnectionService mXmppConnectionService
.getMessageGenerator() .getMessageGenerator()
.sessionReject(from, sessionId); .sessionReject(from, sessionId);
@ -494,10 +495,11 @@ public class JingleConnectionManager extends AbstractConnectionManager {
if (remoteMsgId == null) { if (remoteMsgId == null) {
return; return;
} }
final MessagePacket errorMessage = new MessagePacket(); final var errorMessage =
new im.conversations.android.xmpp.model.stanza.Message();
errorMessage.setTo(from); errorMessage.setTo(from);
errorMessage.setId(remoteMsgId); errorMessage.setId(remoteMsgId);
errorMessage.setType(MessagePacket.TYPE_ERROR); errorMessage.setType(im.conversations.android.xmpp.model.stanza.Message.Type.ERROR);
final Element error = errorMessage.addChild("error"); final Element error = errorMessage.addChild("error");
error.setAttribute("code", "404"); error.setAttribute("code", "404");
error.setAttribute("type", "cancel"); error.setAttribute("type", "cancel");
@ -722,7 +724,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
rtpSessionProposal.sessionId, rtpSessionProposal.sessionId,
RtpEndUserState.RETRACTED); RtpEndUserState.RETRACTED);
} }
final MessagePacket messagePacket = final var messagePacket =
mXmppConnectionService.getMessageGenerator().sessionRetract(rtpSessionProposal); mXmppConnectionService.getMessageGenerator().sessionRetract(rtpSessionProposal);
writeLogMissedOutgoing( writeLogMissedOutgoing(
account, account,
@ -791,7 +793,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
this.rtpSessionProposals.put(proposal, DeviceDiscoveryState.SEARCHING); this.rtpSessionProposals.put(proposal, DeviceDiscoveryState.SEARCHING);
mXmppConnectionService.notifyJingleRtpConnectionUpdate( mXmppConnectionService.notifyJingleRtpConnectionUpdate(
account, proposal.with, proposal.sessionId, RtpEndUserState.FINDING_DEVICE); account, proposal.with, proposal.sessionId, RtpEndUserState.FINDING_DEVICE);
final MessagePacket messagePacket = final var messagePacket =
mXmppConnectionService.getMessageGenerator().sessionProposal(proposal); mXmppConnectionService.getMessageGenerator().sessionProposal(proposal);
mXmppConnectionService.sendMessagePacket(account, messagePacket); mXmppConnectionService.sendMessagePacket(account, messagePacket);
return proposal; return proposal;
@ -801,7 +803,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
public void sendJingleMessageFinish( public void sendJingleMessageFinish(
final Contact contact, final String sessionId, final Reason reason) { final Contact contact, final String sessionId, final Reason reason) {
final var account = contact.getAccount(); final var account = contact.getAccount();
final MessagePacket messagePacket = final var messagePacket =
mXmppConnectionService mXmppConnectionService
.getMessageGenerator() .getMessageGenerator()
.sessionFinish(contact.getJid(), sessionId, reason); .sessionFinish(contact.getJid(), sessionId, reason);
@ -843,7 +845,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
return false; return false;
} }
public void deliverIbbPacket(final Account account, final IqPacket packet) { public void deliverIbbPacket(final Account account, final Iq packet) {
final String sid; final String sid;
final Element payload; final Element payload;
final InbandBytestreamsTransport.PacketType packetType; final InbandBytestreamsTransport.PacketType packetType;
@ -869,7 +871,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
Config.LOGTAG, Config.LOGTAG,
account.getJid().asBareJid() + ": unable to deliver ibb packet. missing sid"); account.getJid().asBareJid() + ": unable to deliver ibb packet. missing sid");
account.getXmppConnection() account.getXmppConnection()
.sendIqPacket(packet.generateResponse(IqPacket.TYPE.ERROR), null); .sendIqPacket(packet.generateResponse(Iq.Type.ERROR), null);
return; return;
} }
for (final AbstractJingleConnection connection : this.connections.values()) { for (final AbstractJingleConnection connection : this.connections.values()) {
@ -880,11 +882,11 @@ public class JingleConnectionManager extends AbstractConnectionManager {
if (inBandTransport.deliverPacket(packetType, packet.getFrom(), payload)) { if (inBandTransport.deliverPacket(packetType, packet.getFrom(), payload)) {
account.getXmppConnection() account.getXmppConnection()
.sendIqPacket( .sendIqPacket(
packet.generateResponse(IqPacket.TYPE.RESULT), null); packet.generateResponse(Iq.Type.RESULT), null);
} else { } else {
account.getXmppConnection() account.getXmppConnection()
.sendIqPacket( .sendIqPacket(
packet.generateResponse(IqPacket.TYPE.ERROR), null); packet.generateResponse(Iq.Type.ERROR), null);
} }
return; return;
} }
@ -895,7 +897,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
Config.LOGTAG, Config.LOGTAG,
account.getJid().asBareJid() + ": unable to deliver ibb packet with sid=" + sid); account.getJid().asBareJid() + ": unable to deliver ibb packet with sid=" + sid);
account.getXmppConnection() account.getXmppConnection()
.sendIqPacket(packet.generateResponse(IqPacket.TYPE.ERROR), null); .sendIqPacket(packet.generateResponse(Iq.Type.ERROR), null);
} }
public void notifyRebound(final Account account) { public void notifyRebound(final Account account) {
@ -946,7 +948,7 @@ public class JingleConnectionManager extends AbstractConnectionManager {
account.getJid().asBareJid() account.getJid().asBareJid()
+ ": resending session proposal to " + ": resending session proposal to "
+ proposal.with); + proposal.with);
final MessagePacket messagePacket = final var messagePacket =
mXmppConnectionService.getMessageGenerator().sessionProposal(proposal); mXmppConnectionService.getMessageGenerator().sessionProposal(proposal);
mXmppConnectionService.sendMessagePacket(account, messagePacket); mXmppConnectionService.sendMessagePacket(account, messagePacket);
} }

View file

@ -45,13 +45,13 @@ import eu.siacs.conversations.xmpp.Jid;
import eu.siacs.conversations.xmpp.jingle.stanzas.Content; import eu.siacs.conversations.xmpp.jingle.stanzas.Content;
import eu.siacs.conversations.xmpp.jingle.stanzas.Group; import eu.siacs.conversations.xmpp.jingle.stanzas.Group;
import eu.siacs.conversations.xmpp.jingle.stanzas.IceUdpTransportInfo; import eu.siacs.conversations.xmpp.jingle.stanzas.IceUdpTransportInfo;
import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket;
import eu.siacs.conversations.xmpp.jingle.stanzas.Proceed; import eu.siacs.conversations.xmpp.jingle.stanzas.Proceed;
import eu.siacs.conversations.xmpp.jingle.stanzas.Propose; import eu.siacs.conversations.xmpp.jingle.stanzas.Propose;
import eu.siacs.conversations.xmpp.jingle.stanzas.Reason; import eu.siacs.conversations.xmpp.jingle.stanzas.Reason;
import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription; import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription;
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
import eu.siacs.conversations.xmpp.stanzas.MessagePacket; import im.conversations.android.xmpp.model.jingle.Jingle;
import im.conversations.android.xmpp.model.stanza.Iq;
import org.webrtc.DtmfSender; import org.webrtc.DtmfSender;
import org.webrtc.EglBase; import org.webrtc.EglBase;
@ -145,24 +145,25 @@ public class JingleRtpConnection extends AbstractJingleConnection
} }
@Override @Override
synchronized void deliverPacket(final JinglePacket jinglePacket) { synchronized void deliverPacket(final Iq iq) {
switch (jinglePacket.getAction()) { final var jingle = iq.getExtension(Jingle.class);
case SESSION_INITIATE -> receiveSessionInitiate(jinglePacket); switch (jingle.getAction()) {
case TRANSPORT_INFO -> receiveTransportInfo(jinglePacket); case SESSION_INITIATE -> receiveSessionInitiate(iq, jingle);
case SESSION_ACCEPT -> receiveSessionAccept(jinglePacket); case TRANSPORT_INFO -> receiveTransportInfo(iq, jingle);
case SESSION_TERMINATE -> receiveSessionTerminate(jinglePacket); case SESSION_ACCEPT -> receiveSessionAccept(iq, jingle);
case CONTENT_ADD -> receiveContentAdd(jinglePacket); case SESSION_TERMINATE -> receiveSessionTerminate(iq);
case CONTENT_ACCEPT -> receiveContentAccept(jinglePacket); case CONTENT_ADD -> receiveContentAdd(iq, jingle);
case CONTENT_REJECT -> receiveContentReject(jinglePacket); case CONTENT_ACCEPT -> receiveContentAccept(iq);
case CONTENT_REMOVE -> receiveContentRemove(jinglePacket); case CONTENT_REJECT -> receiveContentReject(iq, jingle);
case CONTENT_MODIFY -> receiveContentModify(jinglePacket); case CONTENT_REMOVE -> receiveContentRemove(iq, jingle);
case CONTENT_MODIFY -> receiveContentModify(iq, jingle);
default -> { default -> {
respondOk(jinglePacket); respondOk(iq);
Log.d( Log.d(
Config.LOGTAG, Config.LOGTAG,
String.format( String.format(
"%s: received unhandled jingle action %s", "%s: received unhandled jingle action %s",
id.account.getJid().asBareJid(), jinglePacket.getAction())); id.account.getJid().asBareJid(), jingle.getAction()));
} }
} }
} }
@ -193,9 +194,10 @@ public class JingleRtpConnection extends AbstractJingleConnection
return webRTCWrapper.applyDtmfTone(tone); return webRTCWrapper.applyDtmfTone(tone);
} }
private void receiveSessionTerminate(final JinglePacket jinglePacket) { private void receiveSessionTerminate(final Iq jinglePacket) {
respondOk(jinglePacket); respondOk(jinglePacket);
final JinglePacket.ReasonWrapper wrapper = jinglePacket.getReason(); final var jingle = jinglePacket.getExtension(Jingle.class);
final Jingle.ReasonWrapper wrapper = jingle.getReason();
final State previous = this.state; final State previous = this.state;
Log.d( Log.d(
Config.LOGTAG, Config.LOGTAG,
@ -224,7 +226,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
finish(); finish();
} }
private void receiveTransportInfo(final JinglePacket jinglePacket) { private void receiveTransportInfo(final Iq jinglePacket, final Jingle jingle) {
// Due to the asynchronicity of processing session-init we might move from NULL|PROCEED to // Due to the asynchronicity of processing session-init we might move from NULL|PROCEED to
// INITIALIZED only after transport-info has been received // INITIALIZED only after transport-info has been received
if (isInState( if (isInState(
@ -235,7 +237,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
State.SESSION_ACCEPTED)) { State.SESSION_ACCEPTED)) {
final RtpContentMap contentMap; final RtpContentMap contentMap;
try { try {
contentMap = RtpContentMap.of(jinglePacket); contentMap = RtpContentMap.of(jingle);
} catch (final IllegalArgumentException | NullPointerException e) { } catch (final IllegalArgumentException | NullPointerException e) {
Log.d( Log.d(
Config.LOGTAG, Config.LOGTAG,
@ -265,7 +267,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
} }
private void receiveTransportInfo( private void receiveTransportInfo(
final JinglePacket jinglePacket, final RtpContentMap contentMap) { final Iq jinglePacket, final RtpContentMap contentMap) {
final Set<Map.Entry<String, DescriptionTransport<RtpDescription, IceUdpTransportInfo>>> final Set<Map.Entry<String, DescriptionTransport<RtpDescription, IceUdpTransportInfo>>>
candidates = contentMap.contents.entrySet(); candidates = contentMap.contents.entrySet();
final RtpContentMap remote = getRemoteContentMap(); final RtpContentMap remote = getRemoteContentMap();
@ -304,17 +306,17 @@ public class JingleRtpConnection extends AbstractJingleConnection
} }
} }
private void receiveContentAdd(final JinglePacket jinglePacket) { private void receiveContentAdd(final Iq iq, final Jingle jingle) {
final RtpContentMap modification; final RtpContentMap modification;
try { try {
modification = RtpContentMap.of(jinglePacket); modification = RtpContentMap.of(jingle);
modification.requireContentDescriptions(); modification.requireContentDescriptions();
} catch (final RuntimeException e) { } catch (final RuntimeException e) {
Log.d( Log.d(
Config.LOGTAG, Config.LOGTAG,
id.getAccount().getJid().asBareJid() + ": improperly formatted contents", id.getAccount().getJid().asBareJid() + ": improperly formatted contents",
Throwables.getRootCause(e)); Throwables.getRootCause(e));
respondOk(jinglePacket); respondOk(iq);
webRTCWrapper.close(); webRTCWrapper.close();
sendSessionTerminate(Reason.of(e), e.getMessage()); sendSessionTerminate(Reason.of(e), e.getMessage());
return; return;
@ -330,12 +332,12 @@ public class JingleRtpConnection extends AbstractJingleConnection
new FutureCallback<>() { new FutureCallback<>() {
@Override @Override
public void onSuccess(final RtpContentMap rtpContentMap) { public void onSuccess(final RtpContentMap rtpContentMap) {
receiveContentAdd(jinglePacket, rtpContentMap); receiveContentAdd(iq, rtpContentMap);
} }
@Override @Override
public void onFailure(@NonNull Throwable throwable) { public void onFailure(@NonNull Throwable throwable) {
respondOk(jinglePacket); respondOk(iq);
final Throwable rootCause = Throwables.getRootCause(throwable); final Throwable rootCause = Throwables.getRootCause(throwable);
Log.d( Log.d(
Config.LOGTAG, Config.LOGTAG,
@ -349,12 +351,12 @@ public class JingleRtpConnection extends AbstractJingleConnection
}, },
MoreExecutors.directExecutor()); MoreExecutors.directExecutor());
} else { } else {
terminateWithOutOfOrder(jinglePacket); terminateWithOutOfOrder(iq);
} }
} }
private void receiveContentAdd( private void receiveContentAdd(
final JinglePacket jinglePacket, final RtpContentMap modification) { final Iq jinglePacket, final RtpContentMap modification) {
final RtpContentMap remote = getRemoteContentMap(); final RtpContentMap remote = getRemoteContentMap();
if (!Collections.disjoint(modification.getNames(), remote.getNames())) { if (!Collections.disjoint(modification.getNames(), remote.getNames())) {
respondOk(jinglePacket); respondOk(jinglePacket);
@ -406,10 +408,11 @@ public class JingleRtpConnection extends AbstractJingleConnection
} }
} }
private void receiveContentAccept(final JinglePacket jinglePacket) { private void receiveContentAccept(final Iq jinglePacket) {
final var jingle = jinglePacket.getExtension(Jingle.class);
final RtpContentMap receivedContentAccept; final RtpContentMap receivedContentAccept;
try { try {
receivedContentAccept = RtpContentMap.of(jinglePacket); receivedContentAccept = RtpContentMap.of(jingle);
receivedContentAccept.requireContentDescriptions(); receivedContentAccept.requireContentDescriptions();
} catch (final RuntimeException e) { } catch (final RuntimeException e) {
Log.d( Log.d(
@ -494,14 +497,14 @@ public class JingleRtpConnection extends AbstractJingleConnection
updateEndUserState(); updateEndUserState();
} }
private void receiveContentModify(final JinglePacket jinglePacket) { private void receiveContentModify(final Iq jinglePacket, final Jingle jingle) {
if (this.state != State.SESSION_ACCEPTED) { if (this.state != State.SESSION_ACCEPTED) {
terminateWithOutOfOrder(jinglePacket); terminateWithOutOfOrder(jinglePacket);
return; return;
} }
final Map<String, Content.Senders> modification = final Map<String, Content.Senders> modification =
Maps.transformEntries( Maps.transformEntries(
jinglePacket.getJingleContents(), (key, value) -> value.getSenders()); jingle.getJingleContents(), (key, value) -> value.getSenders());
final boolean isInitiator = isInitiator(); final boolean isInitiator = isInitiator();
final RtpContentMap currentOutgoing = this.outgoingContentAdd; final RtpContentMap currentOutgoing = this.outgoingContentAdd;
final RtpContentMap remoteContentMap = this.getRemoteContentMap(); final RtpContentMap remoteContentMap = this.getRemoteContentMap();
@ -604,10 +607,10 @@ public class JingleRtpConnection extends AbstractJingleConnection
return candidateBuilder.build(); return candidateBuilder.build();
} }
private void receiveContentReject(final JinglePacket jinglePacket) { private void receiveContentReject(final Iq jinglePacket, final Jingle jingle) {
final RtpContentMap receivedContentReject; final RtpContentMap receivedContentReject;
try { try {
receivedContentReject = RtpContentMap.of(jinglePacket); receivedContentReject = RtpContentMap.of(jingle);
} catch (final RuntimeException e) { } catch (final RuntimeException e) {
Log.d( Log.d(
Config.LOGTAG, Config.LOGTAG,
@ -660,10 +663,10 @@ public class JingleRtpConnection extends AbstractJingleConnection
+ summary); + summary);
} }
private void receiveContentRemove(final JinglePacket jinglePacket) { private void receiveContentRemove(final Iq jinglePacket, final Jingle jingle) {
final RtpContentMap receivedContentRemove; final RtpContentMap receivedContentRemove;
try { try {
receivedContentRemove = RtpContentMap.of(jinglePacket); receivedContentRemove = RtpContentMap.of(jingle);
receivedContentRemove.requireContentDescriptions(); receivedContentRemove.requireContentDescriptions();
} catch (final RuntimeException e) { } catch (final RuntimeException e) {
Log.d( Log.d(
@ -697,8 +700,8 @@ public class JingleRtpConnection extends AbstractJingleConnection
String.format( String.format(
"%s only supports %s as a means to retract a not yet accepted %s", "%s only supports %s as a means to retract a not yet accepted %s",
BuildConfig.APP_NAME, BuildConfig.APP_NAME,
JinglePacket.Action.CONTENT_REMOVE, Jingle.Action.CONTENT_REMOVE,
JinglePacket.Action.CONTENT_ADD)); Jingle.Action.CONTENT_ADD));
} }
} }
@ -723,10 +726,10 @@ public class JingleRtpConnection extends AbstractJingleConnection
return; return;
} }
this.outgoingContentAdd = null; this.outgoingContentAdd = null;
final JinglePacket retract = final Iq retract =
outgoingContentAdd outgoingContentAdd
.toStub() .toStub()
.toJinglePacket(JinglePacket.Action.CONTENT_REMOVE, id.sessionId); .toJinglePacket(Jingle.Action.CONTENT_REMOVE, id.sessionId);
this.send(retract); this.send(retract);
Log.d( Log.d(
Config.LOGTAG, Config.LOGTAG,
@ -782,16 +785,16 @@ public class JingleRtpConnection extends AbstractJingleConnection
"content addition is receive only. we want to upgrade to 'both'"); "content addition is receive only. we want to upgrade to 'both'");
final RtpContentMap modifiedSenders = final RtpContentMap modifiedSenders =
incomingContentAdd.modifiedSenders(Content.Senders.BOTH); incomingContentAdd.modifiedSenders(Content.Senders.BOTH);
final JinglePacket proposedContentModification = final Iq proposedContentModification =
modifiedSenders modifiedSenders
.toStub() .toStub()
.toJinglePacket(JinglePacket.Action.CONTENT_MODIFY, id.sessionId); .toJinglePacket(Jingle.Action.CONTENT_MODIFY, id.sessionId);
proposedContentModification.setTo(id.with); proposedContentModification.setTo(id.with);
xmppConnectionService.sendIqPacket( xmppConnectionService.sendIqPacket(
id.account, id.account,
proposedContentModification, proposedContentModification,
(account, response) -> { (response) -> {
if (response.getType() == IqPacket.TYPE.RESULT) { if (response.getType() == Iq.Type.RESULT) {
Log.d( Log.d(
Config.LOGTAG, Config.LOGTAG,
id.account.getJid().asBareJid() id.account.getJid().asBareJid()
@ -885,7 +888,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
@Override @Override
public void onFailure(@NonNull final Throwable throwable) { public void onFailure(@NonNull final Throwable throwable) {
failureToPerformAction(JinglePacket.Action.CONTENT_ACCEPT, throwable); failureToPerformAction(Jingle.Action.CONTENT_ACCEPT, throwable);
} }
}, },
MoreExecutors.directExecutor()); MoreExecutors.directExecutor());
@ -897,9 +900,9 @@ public class JingleRtpConnection extends AbstractJingleConnection
} }
private void sendContentAccept(final RtpContentMap contentAcceptMap) { private void sendContentAccept(final RtpContentMap contentAcceptMap) {
final JinglePacket jinglePacket = final Iq iq =
contentAcceptMap.toJinglePacket(JinglePacket.Action.CONTENT_ACCEPT, id.sessionId); contentAcceptMap.toJinglePacket(Jingle.Action.CONTENT_ACCEPT, id.sessionId);
send(jinglePacket); send(iq);
} }
public synchronized void rejectContentAdd() { public synchronized void rejectContentAdd() {
@ -913,20 +916,20 @@ public class JingleRtpConnection extends AbstractJingleConnection
} }
private void rejectContentAdd(final RtpContentMap contentMap) { private void rejectContentAdd(final RtpContentMap contentMap) {
final JinglePacket jinglePacket = final Iq iq =
contentMap contentMap
.toStub() .toStub()
.toJinglePacket(JinglePacket.Action.CONTENT_REJECT, id.sessionId); .toJinglePacket(Jingle.Action.CONTENT_REJECT, id.sessionId);
Log.d( Log.d(
Config.LOGTAG, Config.LOGTAG,
id.getAccount().getJid().asBareJid() id.getAccount().getJid().asBareJid()
+ ": rejecting content " + ": rejecting content "
+ ContentAddition.summary(contentMap)); + ContentAddition.summary(contentMap));
send(jinglePacket); send(iq);
} }
private boolean checkForIceRestart( private boolean checkForIceRestart(
final JinglePacket jinglePacket, final RtpContentMap rtpContentMap) { final Iq jinglePacket, final RtpContentMap rtpContentMap) {
final RtpContentMap existing = getRemoteContentMap(); final RtpContentMap existing = getRemoteContentMap();
final Set<IceUdpTransportInfo.Credentials> existingCredentials; final Set<IceUdpTransportInfo.Credentials> existingCredentials;
final IceUdpTransportInfo.Credentials newCredentials; final IceUdpTransportInfo.Credentials newCredentials;
@ -1005,7 +1008,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
} }
private boolean applyIceRestart( private boolean applyIceRestart(
final JinglePacket jinglePacket, final Iq jinglePacket,
final RtpContentMap restartContentMap, final RtpContentMap restartContentMap,
final boolean isOffer) final boolean isOffer)
throws ExecutionException, InterruptedException { throws ExecutionException, InterruptedException {
@ -1106,7 +1109,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
} }
private ListenableFuture<RtpContentMap> receiveRtpContentMap( private ListenableFuture<RtpContentMap> receiveRtpContentMap(
final JinglePacket jinglePacket, final boolean expectVerification) { final Jingle jinglePacket, final boolean expectVerification) {
try { try {
return receiveRtpContentMap(RtpContentMap.of(jinglePacket), expectVerification); return receiveRtpContentMap(RtpContentMap.of(jinglePacket), expectVerification);
} catch (final Exception e) { } catch (final Exception e) {
@ -1149,12 +1152,12 @@ public class JingleRtpConnection extends AbstractJingleConnection
} }
} }
private void receiveSessionInitiate(final JinglePacket jinglePacket) { private void receiveSessionInitiate(final Iq jinglePacket, final Jingle jingle) {
if (isInitiator()) { if (isInitiator()) {
receiveOutOfOrderAction(jinglePacket, JinglePacket.Action.SESSION_INITIATE); receiveOutOfOrderAction(jinglePacket, Jingle.Action.SESSION_INITIATE);
return; return;
} }
final ListenableFuture<RtpContentMap> future = receiveRtpContentMap(jinglePacket, false); final ListenableFuture<RtpContentMap> future = receiveRtpContentMap(jingle, false);
Futures.addCallback( Futures.addCallback(
future, future,
new FutureCallback<>() { new FutureCallback<>() {
@ -1173,7 +1176,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
} }
private void receiveSessionInitiate( private void receiveSessionInitiate(
final JinglePacket jinglePacket, final RtpContentMap contentMap) { final Iq jinglePacket, final RtpContentMap contentMap) {
try { try {
contentMap.requireContentDescriptions(); contentMap.requireContentDescriptions();
contentMap.requireDTLSFingerprint(true); contentMap.requireDTLSFingerprint(true);
@ -1233,13 +1236,13 @@ public class JingleRtpConnection extends AbstractJingleConnection
} }
} }
private void receiveSessionAccept(final JinglePacket jinglePacket) { private void receiveSessionAccept(final Iq jinglePacket, final Jingle jingle) {
if (isResponder()) { if (isResponder()) {
receiveOutOfOrderAction(jinglePacket, JinglePacket.Action.SESSION_ACCEPT); receiveOutOfOrderAction(jinglePacket, Jingle.Action.SESSION_ACCEPT);
return; return;
} }
final ListenableFuture<RtpContentMap> future = final ListenableFuture<RtpContentMap> future =
receiveRtpContentMap(jinglePacket, this.omemoVerification.hasFingerprint()); receiveRtpContentMap(jingle, this.omemoVerification.hasFingerprint());
Futures.addCallback( Futures.addCallback(
future, future,
new FutureCallback<>() { new FutureCallback<>() {
@ -1264,7 +1267,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
} }
private void receiveSessionAccept( private void receiveSessionAccept(
final JinglePacket jinglePacket, final RtpContentMap contentMap) { final Iq jinglePacket, final RtpContentMap contentMap) {
try { try {
contentMap.requireContentDescriptions(); contentMap.requireContentDescriptions();
contentMap.requireDTLSFingerprint(); contentMap.requireDTLSFingerprint();
@ -1409,7 +1412,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
} }
private void failureToPerformAction( private void failureToPerformAction(
final JinglePacket.Action action, final Throwable throwable) { final Jingle.Action action, final Throwable throwable) {
if (isTerminated()) { if (isTerminated()) {
return; return;
} }
@ -1480,8 +1483,8 @@ public class JingleRtpConnection extends AbstractJingleConnection
return; return;
} }
transitionOrThrow(State.SESSION_ACCEPTED); transitionOrThrow(State.SESSION_ACCEPTED);
final JinglePacket sessionAccept = final Iq sessionAccept =
rtpContentMap.toJinglePacket(JinglePacket.Action.SESSION_ACCEPT, id.sessionId); rtpContentMap.toJinglePacket(Jingle.Action.SESSION_ACCEPT, id.sessionId);
send(sessionAccept); send(sessionAccept);
} }
@ -1951,8 +1954,8 @@ public class JingleRtpConnection extends AbstractJingleConnection
return; return;
} }
this.transitionOrThrow(targetState); this.transitionOrThrow(targetState);
final JinglePacket sessionInitiate = final Iq sessionInitiate =
rtpContentMap.toJinglePacket(JinglePacket.Action.SESSION_INITIATE, id.sessionId); rtpContentMap.toJinglePacket(Jingle.Action.SESSION_INITIATE, id.sessionId);
send(sessionInitiate); send(sessionInitiate);
} }
@ -2020,9 +2023,9 @@ public class JingleRtpConnection extends AbstractJingleConnection
+ contentName); + contentName);
return; return;
} }
final JinglePacket jinglePacket = final Iq iq =
transportInfo.toJinglePacket(JinglePacket.Action.TRANSPORT_INFO, id.sessionId); transportInfo.toJinglePacket(Jingle.Action.TRANSPORT_INFO, id.sessionId);
send(jinglePacket); send(iq);
} }
public RtpEndUserState getEndUserState() { public RtpEndUserState getEndUserState() {
@ -2340,7 +2343,8 @@ public class JingleRtpConnection extends AbstractJingleConnection
this.jingleConnectionManager.ensureConnectionIsRegistered(this); this.jingleConnectionManager.ensureConnectionIsRegistered(this);
this.webRTCWrapper.setup(this.xmppConnectionService); this.webRTCWrapper.setup(this.xmppConnectionService);
this.webRTCWrapper.initializePeerConnection(media, iceServers, trickle); this.webRTCWrapper.initializePeerConnection(media, iceServers, trickle);
this.webRTCWrapper.setMicrophoneEnabledOrThrow(callIntegration.isMicrophoneEnabled()); // this.webRTCWrapper.setMicrophoneEnabledOrThrow(callIntegration.isMicrophoneEnabled());
this.webRTCWrapper.setMicrophoneEnabledOrThrow(true);
} }
private void acceptCallFromProposed() { private void acceptCallFromProposed() {
@ -2375,8 +2379,8 @@ public class JingleRtpConnection extends AbstractJingleConnection
} }
private void sendJingleMessage(final String action, final Jid to) { private void sendJingleMessage(final String action, final Jid to) {
final MessagePacket messagePacket = new MessagePacket(); final var messagePacket = new im.conversations.android.xmpp.model.stanza.Message();
messagePacket.setType(MessagePacket.TYPE_CHAT); // we want to carbon copy those messagePacket.setType(im.conversations.android.xmpp.model.stanza.Message.Type.CHAT); // we want to carbon copy those
messagePacket.setTo(to); messagePacket.setTo(to);
final Element intent = final Element intent =
messagePacket messagePacket
@ -2397,7 +2401,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
private void sendJingleMessageFinish(final Reason reason) { private void sendJingleMessageFinish(final Reason reason) {
final var account = id.getAccount(); final var account = id.getAccount();
final MessagePacket messagePacket = final var messagePacket =
xmppConnectionService xmppConnectionService
.getMessageGenerator() .getMessageGenerator()
.sessionFinish(id.with, id.sessionId, reason); .sessionFinish(id.with, id.sessionId, reason);
@ -2556,34 +2560,34 @@ public class JingleRtpConnection extends AbstractJingleConnection
private void initiateIceRestart(final RtpContentMap rtpContentMap) { private void initiateIceRestart(final RtpContentMap rtpContentMap) {
final RtpContentMap transportInfo = rtpContentMap.transportInfo(); final RtpContentMap transportInfo = rtpContentMap.transportInfo();
final JinglePacket jinglePacket = final Iq iq =
transportInfo.toJinglePacket(JinglePacket.Action.TRANSPORT_INFO, id.sessionId); transportInfo.toJinglePacket(Jingle.Action.TRANSPORT_INFO, id.sessionId);
Log.d(Config.LOGTAG, "initiating ice restart: " + jinglePacket); Log.d(Config.LOGTAG, "initiating ice restart: " + iq);
jinglePacket.setTo(id.with); iq.setTo(id.with);
xmppConnectionService.sendIqPacket( xmppConnectionService.sendIqPacket(
id.account, id.account,
jinglePacket, iq,
(account, response) -> { (response) -> {
if (response.getType() == IqPacket.TYPE.RESULT) { if (response.getType() == Iq.Type.RESULT) {
Log.d(Config.LOGTAG, "received success to our ice restart"); Log.d(Config.LOGTAG, "received success to our ice restart");
setLocalContentMap(rtpContentMap); setLocalContentMap(rtpContentMap);
webRTCWrapper.setIsReadyToReceiveIceCandidates(true); webRTCWrapper.setIsReadyToReceiveIceCandidates(true);
return; return;
} }
if (response.getType() == IqPacket.TYPE.ERROR) { if (response.getType() == Iq.Type.ERROR) {
if (isTieBreak(response)) { if (isTieBreak(response)) {
Log.d(Config.LOGTAG, "received tie-break as result of ice restart"); Log.d(Config.LOGTAG, "received tie-break as result of ice restart");
return; return;
} }
handleIqErrorResponse(response); handleIqErrorResponse(response);
} }
if (response.getType() == IqPacket.TYPE.TIMEOUT) { if (response.getType() == Iq.Type.TIMEOUT) {
handleIqTimeoutResponse(response); handleIqTimeoutResponse(response);
} }
}); });
} }
private boolean isTieBreak(final IqPacket response) { private boolean isTieBreak(final Iq response) {
final Element error = response.findChild("error"); final Element error = response.findChild("error");
return error != null && error.hasChild("tie-break", Namespace.JINGLE_ERRORS); return error != null && error.hasChild("tie-break", Namespace.JINGLE_ERRORS);
} }
@ -2604,7 +2608,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
@Override @Override
public void onFailure(@NonNull Throwable throwable) { public void onFailure(@NonNull Throwable throwable) {
failureToPerformAction(JinglePacket.Action.CONTENT_ADD, throwable); failureToPerformAction(Jingle.Action.CONTENT_ADD, throwable);
} }
}, },
MoreExecutors.directExecutor()); MoreExecutors.directExecutor());
@ -2612,21 +2616,21 @@ public class JingleRtpConnection extends AbstractJingleConnection
private void sendContentAdd(final RtpContentMap contentAdd) { private void sendContentAdd(final RtpContentMap contentAdd) {
final JinglePacket jinglePacket = final Iq iq =
contentAdd.toJinglePacket(JinglePacket.Action.CONTENT_ADD, id.sessionId); contentAdd.toJinglePacket(Jingle.Action.CONTENT_ADD, id.sessionId);
jinglePacket.setTo(id.with); iq.setTo(id.with);
xmppConnectionService.sendIqPacket( xmppConnectionService.sendIqPacket(
id.account, id.account,
jinglePacket, iq,
(connection, response) -> { (response) -> {
if (response.getType() == IqPacket.TYPE.RESULT) { if (response.getType() == Iq.Type.RESULT) {
Log.d( Log.d(
Config.LOGTAG, Config.LOGTAG,
id.getAccount().getJid().asBareJid() id.getAccount().getJid().asBareJid()
+ ": received ACK to our content-add"); + ": received ACK to our content-add");
return; return;
} }
if (response.getType() == IqPacket.TYPE.ERROR) { if (response.getType() == Iq.Type.ERROR) {
if (isTieBreak(response)) { if (isTieBreak(response)) {
this.outgoingContentAdd = null; this.outgoingContentAdd = null;
Log.d(Config.LOGTAG, "received tie-break as result of our content-add"); Log.d(Config.LOGTAG, "received tie-break as result of our content-add");
@ -2634,7 +2638,7 @@ public class JingleRtpConnection extends AbstractJingleConnection
} }
handleIqErrorResponse(response); handleIqErrorResponse(response);
} }
if (response.getType() == IqPacket.TYPE.TIMEOUT) { if (response.getType() == Iq.Type.TIMEOUT) {
handleIqTimeoutResponse(response); handleIqTimeoutResponse(response);
} }
}); });
@ -2782,7 +2786,12 @@ public class JingleRtpConnection extends AbstractJingleConnection
@Override @Override
public void onCallIntegrationMicrophoneEnabled(final boolean enabled) { public void onCallIntegrationMicrophoneEnabled(final boolean enabled) {
this.webRTCWrapper.setMicrophoneEnabled(enabled); // this is called every time we switch audio devices. Thus it would re-enable a microphone
// that was previous disabled by the user. A proper implementation would probably be to
// track user choice and enable the microphone with a userEnabled() &&
// callIntegration.isMicrophoneEnabled() condition
Log.d(Config.LOGTAG, "ignoring onCallIntegrationMicrophoneEnabled(" + enabled + ")");
// this.webRTCWrapper.setMicrophoneEnabled(enabled);
} }
@Override @Override
@ -2827,13 +2836,13 @@ public class JingleRtpConnection extends AbstractJingleConnection
private void discoverIceServers(final OnIceServersDiscovered onIceServersDiscovered) { private void discoverIceServers(final OnIceServersDiscovered onIceServersDiscovered) {
if (id.account.getXmppConnection().getFeatures().externalServiceDiscovery()) { if (id.account.getXmppConnection().getFeatures().externalServiceDiscovery()) {
final IqPacket request = new IqPacket(IqPacket.TYPE.GET); final Iq request = new Iq(Iq.Type.GET);
request.setTo(id.account.getDomain()); request.setTo(id.account.getDomain());
request.addChild("services", Namespace.EXTERNAL_SERVICE_DISCOVERY); request.addChild("services", Namespace.EXTERNAL_SERVICE_DISCOVERY);
xmppConnectionService.sendIqPacket( xmppConnectionService.sendIqPacket(
id.account, id.account,
request, request,
(account, response) -> { (response) -> {
final var iceServers = IceServers.parse(response); final var iceServers = IceServers.parse(response);
if (iceServers.isEmpty()) { if (iceServers.isEmpty()) {
Log.w( Log.w(

View file

@ -18,9 +18,9 @@ import eu.siacs.conversations.xmpp.jingle.stanzas.GenericDescription;
import eu.siacs.conversations.xmpp.jingle.stanzas.GenericTransportInfo; import eu.siacs.conversations.xmpp.jingle.stanzas.GenericTransportInfo;
import eu.siacs.conversations.xmpp.jingle.stanzas.Group; import eu.siacs.conversations.xmpp.jingle.stanzas.Group;
import eu.siacs.conversations.xmpp.jingle.stanzas.IceUdpTransportInfo; import eu.siacs.conversations.xmpp.jingle.stanzas.IceUdpTransportInfo;
import eu.siacs.conversations.xmpp.jingle.stanzas.JinglePacket;
import eu.siacs.conversations.xmpp.jingle.stanzas.OmemoVerifiedIceUdpTransportInfo; import eu.siacs.conversations.xmpp.jingle.stanzas.OmemoVerifiedIceUdpTransportInfo;
import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription; import eu.siacs.conversations.xmpp.jingle.stanzas.RtpDescription;
import im.conversations.android.xmpp.model.jingle.Jingle;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
@ -39,7 +39,7 @@ public class RtpContentMap extends AbstractContentMap<RtpDescription, IceUdpTran
super(group, contents); super(group, contents);
} }
public static RtpContentMap of(final JinglePacket jinglePacket) { public static RtpContentMap of(final Jingle jinglePacket) {
final Map<String, DescriptionTransport<RtpDescription, IceUdpTransportInfo>> contents = final Map<String, DescriptionTransport<RtpDescription, IceUdpTransportInfo>> contents =
of(jinglePacket.getJingleContents()); of(jinglePacket.getJingleContents());
if (isOmemoVerified(contents)) { if (isOmemoVerified(contents)) {
@ -53,7 +53,7 @@ public class RtpContentMap extends AbstractContentMap<RtpDescription, IceUdpTran
Map<String, DescriptionTransport<RtpDescription, IceUdpTransportInfo>> contents) { Map<String, DescriptionTransport<RtpDescription, IceUdpTransportInfo>> contents) {
final Collection<DescriptionTransport<RtpDescription, IceUdpTransportInfo>> values = final Collection<DescriptionTransport<RtpDescription, IceUdpTransportInfo>> values =
contents.values(); contents.values();
if (values.size() == 0) { if (values.isEmpty()) {
return false; return false;
} }
for (final DescriptionTransport<RtpDescription, IceUdpTransportInfo> descriptionTransport : for (final DescriptionTransport<RtpDescription, IceUdpTransportInfo> descriptionTransport :

View 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);
}
}

View file

@ -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("<", "&lt;");
}
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));
}
}
}

View file

@ -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);
}
}
}

View file

@ -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();
}
}
}

View file

@ -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();
}
}

View 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();
}
}

View 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
}
}

View 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;
}
}
}

View file

@ -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");
}
}

View file

@ -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);
}

View file

@ -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();
}

View file

@ -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);
}

View file

@ -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();
}

View file

@ -0,0 +1,8 @@
package im.conversations.android.xmpp.model;
public abstract class DeliveryReceiptRequest extends Extension {
protected DeliveryReceiptRequest(Class<? extends Extension> clazz) {
super(clazz);
}
}

View file

@ -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);
}
}
}

View 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());
}
}
}

View file

@ -0,0 +1,8 @@
package im.conversations.android.xmpp.model;
public abstract class StreamElement extends Extension {
protected StreamElement(Class<? extends StreamElement> clazz) {
super(clazz);
}
}

View file

@ -0,0 +1,8 @@
package im.conversations.android.xmpp.model;
public abstract class StreamFeature extends Extension{
public StreamFeature(Class<? extends StreamFeature> clazz) {
super(clazz);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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;

View file

@ -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);
}
}

View file

@ -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");
}
}

View file

@ -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);
}
}

View file

@ -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());
}
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}
}

View file

@ -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());
}
}

View file

@ -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);
}
}

View file

@ -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();
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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();
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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