From b78d45c7cc6ecc944ae31d628a7233d0d95aeb33 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 7 Jun 2020 12:47:03 +0200 Subject: [PATCH 01/29] fix Jingle FT candidate selection for equal priority. fixes #3771 --- .../xmpp/jingle/JingleCandidate.java | 3 +- .../jingle/JingleFileTransferConnection.java | 48 +++++++------------ 2 files changed, 19 insertions(+), 32 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleCandidate.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleCandidate.java index d1bfc987a..0fd0eabaf 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleCandidate.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleCandidate.java @@ -147,7 +147,6 @@ public class JingleCandidate { } public String toString() { - return this.getHost() + ":" + this.getPort() + " (prio=" - + this.getPriority() + ")"; + return String.format("%s:%s (priority=%s,ours=%s)", getHost(), getPort(), getPriority(), isOurs()); } } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleFileTransferConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleFileTransferConnection.java index 2f84f7c45..36948d48b 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleFileTransferConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleFileTransferConnection.java @@ -5,6 +5,8 @@ import android.util.Log; import com.google.common.base.Preconditions; import com.google.common.collect.Collections2; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.Iterables; import java.io.File; import java.io.FileInputStream; @@ -442,7 +444,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple try { senders = content.getSenders(); } catch (final Exception e) { - senders = Content.Senders.INITIATOR; + senders = Content.Senders.INITIATOR; } this.contentSenders = senders; this.contentName = content.getAttribute("name"); @@ -825,8 +827,9 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple this.sendFallbackToIbb(); } } else { + //TODO at this point we can already close other connections to free some resources final JingleCandidate candidate = connection.getCandidate(); - Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": elected candidate " + candidate.getHost() + ":" + candidate.getPort()); + Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": elected candidate " + candidate.toString()); this.mJingleStatus = JINGLE_STATUS_TRANSMITTING; if (connection.needsActivation()) { if (connection.getCandidate().isOurs()) { @@ -875,38 +878,23 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple } private JingleSocks5Transport chooseConnection() { - JingleSocks5Transport connection = null; - for (Entry cursor : connections - .entrySet()) { - JingleSocks5Transport currentConnection = cursor.getValue(); - // Log.d(Config.LOGTAG,"comparing candidate: "+currentConnection.getCandidate().toString()); - if (currentConnection.isEstablished() - && (currentConnection.getCandidate().isUsedByCounterpart() || (!currentConnection - .getCandidate().isOurs()))) { - // Log.d(Config.LOGTAG,"is usable"); - if (connection == null) { - connection = currentConnection; - } else { - if (connection.getCandidate().getPriority() < currentConnection - .getCandidate().getPriority()) { - connection = currentConnection; - } else if (connection.getCandidate().getPriority() == currentConnection - .getCandidate().getPriority()) { - // Log.d(Config.LOGTAG,"found two candidates with same priority"); + final List establishedConnections = FluentIterable.from(connections.entrySet()) + .transform(Entry::getValue) + .filter(c -> (c != null && c.isEstablished() && (c.getCandidate().isUsedByCounterpart() || !c.getCandidate().isOurs()))) + .toSortedList((a, b) -> { + final int compare = Integer.compare(b.getCandidate().getPriority(), a.getCandidate().getPriority()); + if (compare == 0) { if (isInitiator()) { - if (currentConnection.getCandidate().isOurs()) { - connection = currentConnection; - } + //pick the one we sent a candidate-used for (meaning not ours) + return a.getCandidate().isOurs() ? 1 : -1; } else { - if (!currentConnection.getCandidate().isOurs()) { - connection = currentConnection; - } + //pick the one they sent a candidate-used for (meaning ours) + return a.getCandidate().isOurs() ? -1 : 1; } } - } - } - } - return connection; + return compare; + }); + return Iterables.getFirst(establishedConnections, null); } private void sendSuccess() { From 1853242c6682c863d5524be0a1ee74c2c15e519e Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 7 Jun 2020 14:59:55 +0200 Subject: [PATCH 02/29] do not throw when finishing jingle ft twice. fixes #3765 the state machine in jingle file transfer does not prevent that the connection is being finished twice --- .../conversations/xmpp/jingle/JingleConnectionManager.java | 4 ++++ .../siacs/conversations/xmpp/jingle/JingleRtpConnection.java | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java index 4a1f7148f..34b6ac9ab 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java @@ -447,6 +447,10 @@ public class JingleConnectionManager extends AbstractConnectionManager { } void finishConnection(final AbstractJingleConnection connection) { + this.connections.remove(connection.getId()); + } + + void finishConnectionOrThrow(final AbstractJingleConnection connection) { final AbstractJingleConnection.Id id = connection.getId(); if (this.connections.remove(id) == null) { throw new IllegalStateException(String.format("Unable to finish connection with id=%s", id.toString())); diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java index 8ff496835..835560aa1 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -1189,7 +1189,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web if (isTerminated()) { this.cancelRingingTimeout(); this.webRTCWrapper.verifyClosed(); - this.jingleConnectionManager.finishConnection(this); + this.jingleConnectionManager.finishConnectionOrThrow(this); } else { throw new IllegalStateException(String.format("Unable to call finish from %s", this.state)); } From 7e2d87f39c683c8ca0cab1be4058e0ca1fbd6e3d Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 9 Jun 2020 21:08:27 +0200 Subject: [PATCH 03/29] =?UTF-8?q?recover=20if=20attachImage=20can=E2=80=99?= =?UTF-8?q?t=20generate=20scalled=20down=20version=20of=20image.=20fixes?= =?UTF-8?q?=20#3773?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../persistance/FileBackend.java | 17 +++++++----- .../services/XmppConnectionService.java | 26 +++++++++++-------- .../conversations/ui/util/Attachment.java | 22 ++++++++-------- 3 files changed, 36 insertions(+), 29 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java index 1ff94c7c0..98d0f3de8 100644 --- a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java @@ -704,7 +704,7 @@ public class FileBackend { return pos > 0 ? filename.substring(pos + 1) : null; } - private void copyImageToPrivateStorage(File file, Uri image, int sampleSize) throws FileCopyException { + private void copyImageToPrivateStorage(File file, Uri image, int sampleSize) throws FileCopyException, NotAnImageFileException { file.getParentFile().mkdirs(); InputStream is = null; OutputStream os = null; @@ -724,7 +724,7 @@ public class FileBackend { originalBitmap = BitmapFactory.decodeStream(is, null, options); is.close(); if (originalBitmap == null) { - throw new FileCopyException(R.string.error_not_an_image_file); + throw new NotAnImageFileException(); } Bitmap scaledBitmap = resize(originalBitmap, Config.IMAGE_SIZE); int rotation = getRotation(image); @@ -763,12 +763,12 @@ public class FileBackend { } } - public void copyImageToPrivateStorage(File file, Uri image) throws FileCopyException { + public void copyImageToPrivateStorage(File file, Uri image) throws FileCopyException, NotAnImageFileException { Log.d(Config.LOGTAG, "copy image (" + image.toString() + ") to private storage " + file.getAbsolutePath()); copyImageToPrivateStorage(file, image, 0); } - public void copyImageToPrivateStorage(Message message, Uri image) throws FileCopyException { + public void copyImageToPrivateStorage(Message message, Uri image) throws FileCopyException, NotAnImageFileException { switch (Config.IMAGE_FORMAT) { case JPEG: message.setRelativeFilePath(message.getUuid() + ".jpg"); @@ -1420,11 +1420,14 @@ public class FileBackend { } } - public class FileCopyException extends Exception { - private static final long serialVersionUID = -1010013599132881427L; + public static class NotAnImageFileException extends Exception { + + } + + public static class FileCopyException extends Exception { private int resId; - public FileCopyException(int resId) { + private FileCopyException(int resId) { this.resId = resId; } diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 6f1ff65cc..ebac40f99 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -567,19 +567,23 @@ public class XmppConnectionService extends Service { mFileAddingExecutor.execute(() -> { try { getFileBackend().copyImageToPrivateStorage(message, uri); - if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) { - final PgpEngine pgpEngine = getPgpEngine(); - if (pgpEngine != null) { - pgpEngine.encrypt(message, callback); - } else if (callback != null) { - callback.error(R.string.unable_to_connect_to_keychain, null); - } - } else { - sendMessage(message); - callback.success(message); - } + } catch (FileBackend.NotAnImageFileException e) { + attachFileToConversation(conversation, uri, mimeType, callback); + return; } catch (final FileBackend.FileCopyException e) { callback.error(e.getResId(), message); + return; + } + if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) { + final PgpEngine pgpEngine = getPgpEngine(); + if (pgpEngine != null) { + pgpEngine.encrypt(message, callback); + } else if (callback != null) { + callback.error(R.string.unable_to_connect_to_keychain, null); + } + } else { + sendMessage(message); + callback.success(message); } }); } diff --git a/src/main/java/eu/siacs/conversations/ui/util/Attachment.java b/src/main/java/eu/siacs/conversations/ui/util/Attachment.java index d4bd33bb2..14d5749a1 100644 --- a/src/main/java/eu/siacs/conversations/ui/util/Attachment.java +++ b/src/main/java/eu/siacs/conversations/ui/util/Attachment.java @@ -35,7 +35,6 @@ import android.content.Intent; import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; -import android.util.Log; import java.io.File; import java.util.ArrayList; @@ -43,7 +42,6 @@ import java.util.Collections; import java.util.List; import java.util.UUID; -import eu.siacs.conversations.Config; import eu.siacs.conversations.utils.Compatibility; import eu.siacs.conversations.utils.MimeUtils; @@ -113,7 +111,7 @@ public class Attachment implements Parcelable { } public static boolean canBeSendInband(final List attachments) { - for(Attachment attachment : attachments) { + for (Attachment attachment : attachments) { if (attachment.type != Type.LOCATION) { return false; } @@ -122,21 +120,21 @@ public class Attachment implements Parcelable { } public static List of(final Context context, Uri uri, Type type) { - final String mime = type == Type.LOCATION ?null :MimeUtils.guessMimeTypeFromUri(context, uri); + final String mime = type == Type.LOCATION ? null : MimeUtils.guessMimeTypeFromUri(context, uri); return Collections.singletonList(new Attachment(uri, type, mime)); } public static List of(final Context context, List uris) { List attachments = new ArrayList<>(); - for(Uri uri : uris) { + for (Uri uri : uris) { final String mime = MimeUtils.guessMimeTypeFromUri(context, uri); - attachments.add(new Attachment(uri, mime != null && mime.startsWith("image/") ? Type.IMAGE : Type.FILE,mime)); + attachments.add(new Attachment(uri, mime != null && isImage(mime) ? Type.IMAGE : Type.FILE, mime)); } return attachments; } public static Attachment of(UUID uuid, final File file, String mime) { - return new Attachment(uuid, Uri.fromFile(file),mime != null && (mime.startsWith("image/") || mime.startsWith("video/")) ? Type.IMAGE : Type.FILE, mime); + return new Attachment(uuid, Uri.fromFile(file), mime != null && (isImage(mime) || mime.startsWith("video/")) ? Type.IMAGE : Type.FILE, mime); } public static List extractAttachments(final Context context, final Intent intent, Type type) { @@ -151,9 +149,7 @@ public class Attachment implements Parcelable { if (clipData != null) { for (int i = 0; i < clipData.getItemCount(); ++i) { final Uri uri = clipData.getItemAt(i).getUri(); - Log.d(Config.LOGTAG,"uri="+uri+" contentType="+contentType); final String mime = MimeUtils.guessMimeTypeFromUriAndMime(context, uri, contentType); - Log.d(Config.LOGTAG,"mime="+mime); uris.add(new Attachment(uri, type, mime)); } } @@ -165,12 +161,12 @@ public class Attachment implements Parcelable { } public boolean renderThumbnail() { - return type == Type.IMAGE || (type == Type.FILE && mime != null && renderFileThumbnail(mime)); + return type == Type.IMAGE || (type == Type.FILE && mime != null && renderFileThumbnail(mime)); } private static boolean renderFileThumbnail(final String mime) { return mime.startsWith("video/") - || mime.startsWith("image/") + || isImage(mime) || (Compatibility.runsTwentyOne() && "application/pdf".equals(mime)); } @@ -181,4 +177,8 @@ public class Attachment implements Parcelable { public UUID getUuid() { return uuid; } + + private static boolean isImage(final String mime) { + return mime.startsWith("image/") && !mime.equals("image/svg+xml"); + } } From 552e17e39abe7af0faafcfc8580cb74a060716eb Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 11 Jun 2020 21:17:13 +0200 Subject: [PATCH 04/29] remember terminal RTP session state MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit if the activity is not connected during finish it won’t receive the last end user state. this code remembers it even if the actual session is already gone. so when activity reconnects and we can’t find the real rtp session we can look up the last state instead. --- .../conversations/ui/RtpSessionActivity.java | 23 ++++++++++++++++- .../xmpp/jingle/JingleConnectionManager.java | 25 ++++++++++++++----- .../xmpp/jingle/JingleRtpConnection.java | 2 +- 3 files changed, 42 insertions(+), 8 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java index b7e018abf..9cfda2174 100644 --- a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java @@ -53,6 +53,7 @@ import eu.siacs.conversations.utils.PermissionUtils; import eu.siacs.conversations.utils.TimeFrameUtils; import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xmpp.jingle.AbstractJingleConnection; +import eu.siacs.conversations.xmpp.jingle.JingleConnectionManager; import eu.siacs.conversations.xmpp.jingle.JingleRtpConnection; import eu.siacs.conversations.xmpp.jingle.Media; import eu.siacs.conversations.xmpp.jingle.RtpEndUserState; @@ -427,7 +428,13 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe final WeakReference reference = xmppConnectionService.getJingleConnectionManager() .findJingleRtpConnection(account, with, sessionId); if (reference == null || reference.get() == null) { - throw new IllegalStateException("failed to initialize activity with running rtp session. session not found"); + final JingleConnectionManager.TerminatedRtpSession terminatedRtpSession = xmppConnectionService + .getJingleConnectionManager().getTerminalSessionState(with, sessionId); + if (terminatedRtpSession == null) { + throw new IllegalStateException("failed to initialize activity with running rtp session. session not found"); + } + initializeWithTerminatedSessionState(account, with, terminatedRtpSession); + return true; } this.rtpConnectionReference = reference; final RtpEndUserState currentState = requireRtpConnection().getEndUserState(); @@ -451,6 +458,20 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe return false; } + private void initializeWithTerminatedSessionState(final Account account, final Jid with, final JingleConnectionManager.TerminatedRtpSession terminatedRtpSession) { + Log.d(Config.LOGTAG,"initializeWithTerminatedSessionState()"); + if (terminatedRtpSession.state == RtpEndUserState.ENDED) { + finish(); + return; + } + RtpEndUserState state = terminatedRtpSession.state; + resetIntent(account, with, terminatedRtpSession.state, terminatedRtpSession.media); + updateButtonConfiguration(state); + updateStateDisplay(state); + updateProfilePicture(state); + binding.with.setText(account.getRoster().getContact(with).getDisplayName()); + } + private void reInitializeActivityWithRunningRtpSession(final Account account, Jid with, String sessionId) { runOnUiThread(() -> initializeActivityWithRunningRtpSession(account, with, sessionId)); resetIntent(account, with, sessionId); diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java index 34b6ac9ab..03bafb275 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleConnectionManager.java @@ -11,7 +11,6 @@ import com.google.common.cache.CacheBuilder; import com.google.common.collect.Collections2; import com.google.common.collect.ComparisonChain; import com.google.common.collect.ImmutableSet; -import com.google.j2objc.annotations.Weak; import java.lang.ref.WeakReference; import java.security.SecureRandom; @@ -57,8 +56,8 @@ public class JingleConnectionManager extends AbstractConnectionManager { private final HashMap rtpSessionProposals = new HashMap<>(); private final ConcurrentHashMap connections = new ConcurrentHashMap<>(); - private final Cache endedSessions = CacheBuilder.newBuilder() - .expireAfterWrite(30, TimeUnit.MINUTES) + private final Cache terminatedSessions = CacheBuilder.newBuilder() + .expireAfterWrite(24, TimeUnit.HOURS) .build(); private HashMap primaryCandidates = new HashMap<>(); @@ -92,7 +91,7 @@ public class JingleConnectionManager extends AbstractConnectionManager { if (FileTransferDescription.NAMESPACES.contains(descriptionNamespace)) { connection = new JingleFileTransferConnection(this, id, from); } else if (Namespace.JINGLE_APPS_RTP.equals(descriptionNamespace) && !usesTor(account)) { - final boolean sessionEnded = this.endedSessions.asMap().containsKey(PersistableSessionId.of(id)); + final boolean sessionEnded = this.terminatedSessions.asMap().containsKey(PersistableSessionId.of(id)); final boolean stranger = isWithStrangerAndStrangerNotificationsAreOff(account, id.with); if (isBusy() || sessionEnded || stranger) { Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": rejected session with " + id.with + " because busy. sessionEnded=" + sessionEnded + ", stranger=" + stranger); @@ -684,8 +683,12 @@ public class JingleConnectionManager extends AbstractConnectionManager { throw e; } - void endSession(AbstractJingleConnection.Id id, final AbstractJingleConnection.State state) { - this.endedSessions.put(PersistableSessionId.of(id), state); + void setTerminalSessionState(AbstractJingleConnection.Id id, final RtpEndUserState state, final Set media) { + this.terminatedSessions.put(PersistableSessionId.of(id), new TerminatedRtpSession(state, media)); + } + + public TerminatedRtpSession getTerminalSessionState(final Jid with, final String sessionId) { + return this.terminatedSessions.getIfPresent(new PersistableSessionId(with, sessionId)); } private static class PersistableSessionId { @@ -716,6 +719,16 @@ public class JingleConnectionManager extends AbstractConnectionManager { } } + public static class TerminatedRtpSession { + public final RtpEndUserState state; + public final Set media; + + TerminatedRtpSession(RtpEndUserState state, Set media) { + this.state = state; + this.media = media; + } + } + public enum DeviceDiscoveryState { SEARCHING, DISCOVERED, FAILED; diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java index 835560aa1..2f8486faa 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -912,7 +912,6 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web } if (isInState(State.PROCEED)) { Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": ending call while in state PROCEED just means ending the connection"); - this.jingleConnectionManager.endSession(id, State.TERMINATED_SUCCESS); this.webRTCWrapper.close(); transitionOrThrow(State.TERMINATED_SUCCESS); //arguably this wasn't success; but not a real failure either this.finish(); @@ -1189,6 +1188,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web if (isTerminated()) { this.cancelRingingTimeout(); this.webRTCWrapper.verifyClosed(); + this.jingleConnectionManager.setTerminalSessionState(id, getEndUserState(), getMedia()); this.jingleConnectionManager.finishConnectionOrThrow(this); } else { throw new IllegalStateException(String.format("Unable to call finish from %s", this.state)); From f39daf565a67cacb88025ccce9c6c3734038c967 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 11 Jun 2020 21:22:49 +0200 Subject: [PATCH 05/29] upgrade libwebrtc to m83. fixes #3767 --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 98e9bd2f2..0a54ad489 100644 --- a/build.gradle +++ b/build.gradle @@ -80,7 +80,7 @@ dependencies { implementation 'com.squareup.okhttp3:okhttp:3.12.12' implementation 'com.google.guava:guava:27.1-android' quicksyImplementation 'io.michaelrocks:libphonenumber-android:8.11.1' - implementation fileTree(include: ['libwebrtc-m81.aar'], dir: 'libs') + implementation fileTree(include: ['libwebrtc-m83.aar'], dir: 'libs') } ext { From 2cd0cc50d068c4c5997d3d61959dc7b1ff93833d Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 11 Jun 2020 21:32:52 +0200 Subject: [PATCH 06/29] point travis to m83.aar --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ac5f1ff91..0a938b3e4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ android: - '.+' before_script: - mkdir libs - - wget -O libs/libwebrtc-m81.aar http://gultsch.de/files/libwebrtc-m81.aar + - wget -O libs/libwebrtc-m81.aar http://gultsch.de/files/libwebrtc-m83.aar script: - ./gradlew assembleConversationsFreeSystemRelease - ./gradlew assembleQuicksyFreeCompatRelease From adb3c77d313faed91238476510b5be7062dbb4bb Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 11 Jun 2020 21:41:22 +0200 Subject: [PATCH 07/29] fix travis for real --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 0a938b3e4..d2392afab 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ android: - '.+' before_script: - mkdir libs - - wget -O libs/libwebrtc-m81.aar http://gultsch.de/files/libwebrtc-m83.aar + - wget -O libs/libwebrtc-m83.aar http://gultsch.de/files/libwebrtc-m83.aar script: - ./gradlew assembleConversationsFreeSystemRelease - ./gradlew assembleQuicksyFreeCompatRelease From 644ad995204d7fc06ff7a39621d45fa2dae82109 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Fri, 12 Jun 2020 07:56:59 +0200 Subject: [PATCH 08/29] create rtp end user state for connection lost. fixes #3769 --- .../eu/siacs/conversations/ui/RtpSessionActivity.java | 11 ++++++++++- .../xmpp/jingle/JingleRtpConnection.java | 4 ++-- .../conversations/xmpp/jingle/RtpEndUserState.java | 1 + src/main/res/values/strings.xml | 1 + 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java index 9cfda2174..848c447a1 100644 --- a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java @@ -78,6 +78,7 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe RtpEndUserState.APPLICATION_ERROR, RtpEndUserState.DECLINED_OR_BUSY, RtpEndUserState.CONNECTIVITY_ERROR, + RtpEndUserState.CONNECTIVITY_LOST_ERROR, RtpEndUserState.RETRACTED ); private static final String PROXIMITY_WAKE_LOCK_TAG = "conversations:in-rtp-session"; @@ -533,6 +534,9 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe case CONNECTIVITY_ERROR: setTitle(R.string.rtp_state_connectivity_error); break; + case CONNECTIVITY_LOST_ERROR: + setTitle(R.string.rtp_state_connectivity_lost_error); + break; case RETRACTED: setTitle(R.string.rtp_state_retracted); break; @@ -598,7 +602,12 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe this.binding.acceptCall.setOnClickListener(this::recordVoiceMail); this.binding.acceptCall.setImageResource(R.drawable.ic_voicemail_white_24dp); this.binding.acceptCall.setVisibility(View.VISIBLE); - } else if (asList(RtpEndUserState.CONNECTIVITY_ERROR, RtpEndUserState.APPLICATION_ERROR, RtpEndUserState.RETRACTED).contains(state)) { + } else if (asList( + RtpEndUserState.CONNECTIVITY_ERROR, + RtpEndUserState.CONNECTIVITY_LOST_ERROR, + RtpEndUserState.APPLICATION_ERROR, + RtpEndUserState.RETRACTED + ).contains(state)) { this.binding.rejectCall.setOnClickListener(this::exit); this.binding.rejectCall.setImageResource(R.drawable.ic_clear_white_48dp); this.binding.rejectCall.setVisibility(View.VISIBLE); diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java index 2f8486faa..4dadb599d 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -814,7 +814,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web } else if (state == PeerConnection.PeerConnectionState.CLOSED) { return RtpEndUserState.ENDING_CALL; } else { - return RtpEndUserState.CONNECTIVITY_ERROR; + return rtpConnectionStarted == 0 ? RtpEndUserState.CONNECTIVITY_ERROR : RtpEndUserState.CONNECTIVITY_LOST_ERROR; } case REJECTED: case TERMINATED_DECLINED_OR_BUSY: @@ -831,7 +831,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web case RETRACTED_RACED: return RtpEndUserState.RETRACTED; case TERMINATED_CONNECTIVITY_ERROR: - return RtpEndUserState.CONNECTIVITY_ERROR; + return rtpConnectionStarted == 0 ? RtpEndUserState.CONNECTIVITY_ERROR : RtpEndUserState.CONNECTIVITY_LOST_ERROR; case TERMINATED_APPLICATION_FAILURE: return RtpEndUserState.APPLICATION_ERROR; } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpEndUserState.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpEndUserState.java index 398777cfe..3b97fcbc7 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpEndUserState.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpEndUserState.java @@ -11,6 +11,7 @@ public enum RtpEndUserState { ENDED, //close UI DECLINED_OR_BUSY, //other party declined; no retry button CONNECTIVITY_ERROR, //network error; retry button + CONNECTIVITY_LOST_ERROR, //network error but for call duration > 0 RETRACTED, //user pressed home or power button during 'ringing' - shows retry button APPLICATION_ERROR //something rather bad happened; libwebrtc failed or we got in IQ-error } diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 679e37006..a6d951b56 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -903,6 +903,7 @@ Ringing Busy Could not connect call + Connection lost Retracted call App failure Hang up From b7f3b4333e637d56787182afff83195034259414 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Fri, 12 Jun 2020 08:26:33 +0200 Subject: [PATCH 09/29] show help button on certain error conditions in RTP session. fixes #3770 --- .../res/menu/activity_rtp_session.xml | 11 ++++ .../java/eu/siacs/conversations/Config.java | 2 + .../conversations/ui/RtpSessionActivity.java | 54 +++++++++++++++++- .../res/drawable-hdpi/ic_help_white_24dp.png | Bin 0 -> 476 bytes .../res/drawable-mdpi/ic_help_white_24dp.png | Bin 0 -> 304 bytes .../res/drawable-xhdpi/ic_help_white_24dp.png | Bin 0 -> 585 bytes .../drawable-xxhdpi/ic_help_white_24dp.png | Bin 0 -> 842 bytes .../drawable-xxxhdpi/ic_help_white_24dp.png | Bin 0 -> 1132 bytes src/main/res/values/attrs.xml | 1 + src/main/res/values/strings.xml | 1 + src/main/res/values/themes.xml | 2 + 11 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 src/conversations/res/menu/activity_rtp_session.xml create mode 100644 src/main/res/drawable-hdpi/ic_help_white_24dp.png create mode 100644 src/main/res/drawable-mdpi/ic_help_white_24dp.png create mode 100644 src/main/res/drawable-xhdpi/ic_help_white_24dp.png create mode 100644 src/main/res/drawable-xxhdpi/ic_help_white_24dp.png create mode 100644 src/main/res/drawable-xxxhdpi/ic_help_white_24dp.png diff --git a/src/conversations/res/menu/activity_rtp_session.xml b/src/conversations/res/menu/activity_rtp_session.xml new file mode 100644 index 000000000..540a9def9 --- /dev/null +++ b/src/conversations/res/menu/activity_rtp_session.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/src/main/java/eu/siacs/conversations/Config.java b/src/main/java/eu/siacs/conversations/Config.java index 68a158d98..f22561722 100644 --- a/src/main/java/eu/siacs/conversations/Config.java +++ b/src/main/java/eu/siacs/conversations/Config.java @@ -1,6 +1,7 @@ package eu.siacs.conversations; import android.graphics.Bitmap; +import android.net.Uri; import java.util.Collections; import java.util.List; @@ -35,6 +36,7 @@ public final class Config { public static final String LOGTAG = BuildConfig.LOGTAG; public static final Jid BUG_REPORTS = Jid.of("bugs@conversations.im"); + public static final Uri HELP = Uri.parse("https://help.conversations.im"); public static final String DOMAIN_LOCK = null; //only allow account creation for this domain diff --git a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java index 848c447a1..a11f5b053 100644 --- a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java @@ -3,6 +3,7 @@ package eu.siacs.conversations.ui; import android.Manifest; import android.annotation.SuppressLint; import android.app.PictureInPictureParams; +import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; @@ -17,6 +18,8 @@ import android.support.annotation.RequiresApi; import android.support.annotation.StringRes; import android.util.Log; import android.util.Rational; +import android.view.Menu; +import android.view.MenuItem; import android.view.View; import android.view.WindowManager; import android.widget.Toast; @@ -81,6 +84,10 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe RtpEndUserState.CONNECTIVITY_LOST_ERROR, RtpEndUserState.RETRACTED ); + private static final List STATES_SHOWING_HELP_BUTTON = Arrays.asList( + RtpEndUserState.APPLICATION_ERROR, + RtpEndUserState.CONNECTIVITY_ERROR + ); private static final String PROXIMITY_WAKE_LOCK_TAG = "conversations:in-rtp-session"; private static final int REQUEST_ACCEPT_CALL = 0x1111; private WeakReference rtpConnectionReference; @@ -124,6 +131,45 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe setSupportActionBar(binding.toolbar); } + @Override + public boolean onCreateOptionsMenu(final Menu menu) { + getMenuInflater().inflate(R.menu.activity_rtp_session, menu); + final MenuItem help = menu.findItem(R.id.action_help); + help.setVisible(isHelpButtonVisible()); + return super.onCreateOptionsMenu(menu); + } + + private boolean isHelpButtonVisible() { + try { + return STATES_SHOWING_HELP_BUTTON.contains(requireRtpConnection().getEndUserState()); + } catch (IllegalStateException e) { + final Intent intent = getIntent(); + final String state = intent != null ? intent.getStringExtra(EXTRA_LAST_REPORTED_STATE) : null; + if (state != null) { + return STATES_SHOWING_HELP_BUTTON.contains(RtpEndUserState.valueOf(state)); + } else { + return false; + } + } + } + + public boolean onOptionsItemSelected(final MenuItem item) { + if (item.getItemId() == R.id.action_help) { + launchHelpInBrowser(); + return true; + } + return super.onOptionsItemSelected(item); + } + + private void launchHelpInBrowser() { + final Intent intent = new Intent(Intent.ACTION_VIEW, Config.HELP); + try { + startActivity(intent); + } catch (final ActivityNotFoundException e) { + Toast.makeText(this, R.string.no_application_found_to_open_link, Toast.LENGTH_LONG).show(); + } + } + private void endCall(View view) { endCall(); } @@ -302,6 +348,7 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe updateButtonConfiguration(state); updateStateDisplay(state); updateProfilePicture(state); + invalidateOptionsMenu(); } binding.with.setText(account.getRoster().getContact(with).getDisplayName()); } @@ -456,11 +503,12 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe updateStateDisplay(currentState, media); updateButtonConfiguration(currentState, media); updateProfilePicture(currentState); + invalidateOptionsMenu(); return false; } private void initializeWithTerminatedSessionState(final Account account, final Jid with, final JingleConnectionManager.TerminatedRtpSession terminatedRtpSession) { - Log.d(Config.LOGTAG,"initializeWithTerminatedSessionState()"); + Log.d(Config.LOGTAG, "initializeWithTerminatedSessionState()"); if (terminatedRtpSession.state == RtpEndUserState.ENDED) { finish(); return; @@ -470,6 +518,8 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe updateButtonConfiguration(state); updateStateDisplay(state); updateProfilePicture(state); + updateCallDuration(); + invalidateOptionsMenu(); binding.with.setText(account.getRoster().getContact(with).getDisplayName()); } @@ -956,6 +1006,7 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe updateButtonConfiguration(state, media); updateVideoViews(state); updateProfilePicture(state, contact); + invalidateOptionsMenu(); }); if (END_CARD.contains(state)) { final JingleRtpConnection rtpConnection = requireRtpConnection(); @@ -1004,6 +1055,7 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe updateStateDisplay(state); updateButtonConfiguration(state); updateProfilePicture(state); + invalidateOptionsMenu(); }); resetIntent(account, with, state, actionToMedia(currentIntent.getAction())); } diff --git a/src/main/res/drawable-hdpi/ic_help_white_24dp.png b/src/main/res/drawable-hdpi/ic_help_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..5664f9532e7894e749472cd81b678d7a988eba19 GIT binary patch literal 476 zcmV<20VDp2P)FS}u8eRGnR8$o7*^pdpI`;)Zh3C*_?imPqNz#SSTLSv(_UTP{{f$uStd zlCmrp4Mto>!JK>;E@M+6nhc1^F&J`Uj+oatTErMx7-H5HN0)I%7KU*hrI4{tD92#P z34NrdG%|LXMTeTEp~Zknj#$@hXtKqKn1W5_brIBAp+k=$B_%_8Y_rUquAaa1Br%** SC%+K@0000-x$b8_O`@KYB7mZ1_t`D2EOuDo_@yjD$TZ zuk0-YN(a1xHiF17 zH=rnle~NOx`lvWzlMkqR|@d}>rEGRFv6-4y*SaX^%P zKGLT~(Zd=(E*^Qhl^zrPBB>hVN`qFukXD|YlA(i58d0J{ZnUzA%{EgE&_)|inPvx@ z5;?i>nXn5c^&c`!aY*NOHJ60FRWy1yCTvm>m?rFqE=6IDux-^b#0KY_ z@tr{#WY{5WRd)2_6E-D-A%64E34=2DK$yov*|3DofDAUc?wbq-u~}3YSq`vilfgOH zoywpcn>{iLVw5yaWzde@3k5Mx8ee7b47*tcQ6#xz24t{^T|q%qNa~p9GI-1>c4Y-o zBbgJ1WzfkEQK|~UCy6s&%HRQ8MDrAcPZIBBaGxK<@f1W2w=LDNPQ0pusNnWq25)dF zD~KX)Ph_xxOF==*;npgHpSa8@h!LDr#~)mV6-1VOnq`-a!uUk9EXamFe462kUfGeS z8I~1=Zfe9(9dQonR5Zqkp*rG>DIR$usg7tXav?{F2&yB>IxTXegA$Ec=bn-wN1n7+ zXi*xBb3{^yj43_3SjEHTie)<0C?2uM9#M8#pjS6VmKV%YpiC7Hk1Ayf%rHzwcW!~d XljjaTd^%zk00000NkvXXu0mjfI|1}c literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-xxhdpi/ic_help_white_24dp.png b/src/main/res/drawable-xxhdpi/ic_help_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..d49181785bf9d1d1135c6b4cd89f635999741f14 GIT binary patch literal 842 zcmV-Q1GW5#P)DHci#T4?D(6E&bG z6+H+Fs|kjXZiKcTq||kLD}*KF((X>u{CayCB=gSu&P)~$`#e%dN5>nAA}5$-fmP}> z2nlIWXO#tJnWUihq09`+gxIuL;v8>jzcJ2zc5t=LJw~-(IL1@bSY}LlV~AU1vB;qE z!)dn2YLhADfgXM$FP90F4Tq?Zr-vL=dX#uZ-l~+84u^O~l9$Xe!C?XdhM3@MUXi3q zzx>fd1-lv_)1}wY#i!J8H zvq@R5xQR`Tf*f#=zp%M3Hyp$66GhO=4C^#$Qspc?ir_QsM&*L1*u11m2Ji5ksNWct zK^Jvwo+ugP*v-kHm*>3hR{|Ma#BM}MxR2e03})ENmok{d?yl@8vxD7X8LYFHUu5t$ zcH5L>!wg9R88q3;KQibc$r;(OOm_dupqnI*6^|kz*{#Xo2uYgsDGn#d!?X-OC)shu zVV3OH31o1aWET{N1+sdo*qzCHUwNIFW=FvJTS#3Sv5GR{7~iw>FqL4NqJ+GC!|qfMEivi z?y`-mkUJdJext-09@E06NrhoLf4U_M U1!|y=uK)l507*qoM6N<$f*NFcH2?qr literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-xxxhdpi/ic_help_white_24dp.png b/src/main/res/drawable-xxxhdpi/ic_help_white_24dp.png new file mode 100644 index 0000000000000000000000000000000000000000..8eb7241da221d5ae26d010433b34e5a5e5ed5908 GIT binary patch literal 1132 zcmV-y1e5!TP)cs4Zm^ya z%ni^?O3y)>L;Qoe0$D{ziaVH^;TS8l2v%~OCz$FXsi^oKQ-f@i0k)IJ)EPxXGp7Ee zR))y(7p4v>JT@_nnL%o0fh>8xQ>}3+hmFDJi$!2!l4OM#}oo>m}-!%vtxB6BQf1XC^YU?(s3lpXTm6HE<}kSTJQ ztCt5IEaqEz@D}D8WQxm}TO$t!Sxlci$YSoI%uvk?=Bnhu9E+Ki2QOjnF=?4#Kk=E9 z2P?3#S0?BpK7I0FJvKUIf&%gRMjpJ2jR6H?9r0PQxYK;Kn3Ld2op6t||y;apOmM zkYI@DPAdrQxX~;R-p7Sj1tEtUJLSPwxX_>=ti_F20000 + diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index a6d951b56..75d839f0b 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -917,6 +917,7 @@ Missed call Audio call Video call + Help Your microphone is unavailable You can only have one call at a time. Return to ongoing call diff --git a/src/main/res/values/themes.xml b/src/main/res/values/themes.xml index 3a19be555..50262913f 100644 --- a/src/main/res/values/themes.xml +++ b/src/main/res/values/themes.xml @@ -114,6 +114,7 @@ @drawable/ic_delete_black_24dp @drawable/ic_search_white_24dp + @drawable/ic_help_white_24dp @drawable/ic_lock_open_white_24dp @drawable/ic_settings_black_24dp @drawable/ic_share_white_24dp @@ -267,6 +268,7 @@ @drawable/ic_delete_white_24dp @drawable/ic_search_white_24dp + @drawable/ic_help_white_24dp @drawable/ic_lock_open_white_24dp @drawable/ic_settings_white_24dp @drawable/ic_share_white_24dp From 0ba4892d3e4efd71bba98d00e424f75db2f0536e Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Fri, 12 Jun 2020 09:08:09 +0200 Subject: [PATCH 10/29] RTP: write log message on background thread --- .../siacs/conversations/services/XmppConnectionService.java | 4 ++++ .../siacs/conversations/xmpp/jingle/JingleRtpConnection.java | 2 +- src/{conversations => main}/res/menu/activity_rtp_session.xml | 0 3 files changed, 5 insertions(+), 1 deletion(-) rename src/{conversations => main}/res/menu/activity_rtp_session.xml (100%) diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index ebac40f99..a6cc30e98 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -3303,6 +3303,10 @@ public class XmppConnectionService extends Service { updateConversationUi(); } + public void createMessageAsync(final Message message) { + mDatabaseWriterExecutor.execute(()-> databaseBackend.createMessage(message)); + } + public void updateMessage(Message message, String uuid) { if (!databaseBackend.updateMessage(message, uuid)) { Log.e(Config.LOGTAG, "error updated message in DB after edit"); diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java index 4dadb599d..e45b79997 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -1219,7 +1219,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web final Conversational conversational = message.getConversation(); if (conversational instanceof Conversation) { ((Conversation) conversational).add(this.message); - xmppConnectionService.databaseBackend.createMessage(message); + xmppConnectionService.createMessageAsync(message); xmppConnectionService.updateConversationUi(); } else { throw new IllegalStateException("Somehow the conversation in a message was a stub"); diff --git a/src/conversations/res/menu/activity_rtp_session.xml b/src/main/res/menu/activity_rtp_session.xml similarity index 100% rename from src/conversations/res/menu/activity_rtp_session.xml rename to src/main/res/menu/activity_rtp_session.xml From 01bfb5fd56f7f0ec57dd750b290e6b324d2a9581 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Fri, 12 Jun 2020 09:19:56 +0200 Subject: [PATCH 11/29] pulled translations from transifex --- src/main/res/values-de/strings.xml | 2 +- src/main/res/values-ru/strings.xml | 25 +++++++++++++++++++++---- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/main/res/values-de/strings.xml b/src/main/res/values-de/strings.xml index 4529e14de..f35175a36 100644 --- a/src/main/res/values-de/strings.xml +++ b/src/main/res/values-de/strings.xml @@ -901,7 +901,7 @@ Klingelt Besetzt Verbindungsaufbau fehlgeschlagen - Rückrufruf + Anruf zurückgenommen App-Fehler Auflegen Laufender Anruf diff --git a/src/main/res/values-ru/strings.xml b/src/main/res/values-ru/strings.xml index 111a65c43..a5e07707e 100644 --- a/src/main/res/values-ru/strings.xml +++ b/src/main/res/values-ru/strings.xml @@ -30,6 +30,7 @@ только что 1 минуту назад %d мин. назад + %d непрочитанных бесед отправка… Расшифровка сообщения. Подождите… OpenPGP зашифр. сообщение @@ -40,12 +41,14 @@ Модератор Участник Посетитель + Вы хотите удалить %s из своего списка контактов? Беседы, связанные с этим контактом, будут сохранены. Вы хотите заблокировать дальнейшие сообщения от %s? Вы хотите разблокировать пользователя %s? Заблокировать всех пользователей домена %s? Разблокировать всех пользователей домена %s? Контакт заблокирован Заблокирован + Вы хотите удалить %s из избранного? Беседы, связанные с данной закладкой, будут сохранены. Создать новый аккаунт на сервере Изменить пароль на сервере Поделиться с @@ -64,14 +67,22 @@ Сохранить ОК Conversations был неожиданно остановлен + Отправляя отчёты об ошибках, вы помогаете совершенствованию Conversations. Отправить сейчас Больше не спрашивать + Не удалось подключиться к учетной записи + Не удалось подключиться к учетным записям + Нажмите, чтобы настроить учетные записи Прикрепить файл + Контакт не находится в вашем списке контактов. Хотите добавить его? Добавить контакт доставка не удалась + Подготовка к передаче изображения + Подготовка к передаче изображений Обмен файлами. Пожалуйста, подождите… Очистить историю Очистить историю + Вы хотите удалить все сообщения в этой беседе?\n\nВнимание: Данная операция не повлияет на сообщения, хранящиеся на других устройствах или серверах. Удалить файл Вы уверены, что хотите удалить этот файл?\n\nПредупреждение: Данная операция не удалит копии этого файла, хранящиеся на других устройствах или серверах. Закрыть эту беседу @@ -82,16 +93,20 @@ OMEMO зашифр. сообщение v\\OMEMO зашифр. сообщение OpenPGP зашифр. сообщение + Имя уже используется Отправить в незашифрованном виде Расшифровка не удалась. Вероятно, что у вас нет надлежащего ключа. Установите OpenKeychain + Conversations использует OpenKeychain для шифрования и дешифрования сообщений и управления открытыми ключами.\n\nOpenKeychain распространяется под лицензией GPLv3 и доступна для загрузки через F-Droid или Google Play.\n\n(Потребуется перезапуск Conversations после установки.) Перезапуск Установка Пожалуйста, установите OpenKeychain предложение… ожидание… Нет OpenPGP ключа + Conversations не может зашифровать сообщение, потому что ваш собеседник не анонсирует свой открытый ключ.\n\nПожалуйста, попросите вашего собеседника настроить OpenPGP. Нет OpenPGP ключей + Conversations не может зашифровать сообщение, потому что ваши собеседники не анонсируют свои открытые ключи.\n\nПожалуйста, попросите ваших собеседников настроить OpenPGP. Общие Принимать файлы Автоматический приём файлов… @@ -101,6 +116,10 @@ Вибрировать, когда приходят новые сообщения Светодиодное уведомление Мерцание индикатора при получении нового сообщения + Мелодия звонка + Звук уведомления + Звук уведомления о новых сообщениях + Мелодия входящего звонка Грейс-период Дополнительно Не отправлять отчёты об ошибках @@ -258,8 +277,6 @@ размещено на %s Проверка %s на сервере HTTP Вы неподключены. Попробуйте позже - Проверить размер %s - Проверить размер %1$s на %2$s Опции сообщения Цитировать Вставить как цитату @@ -676,7 +693,7 @@ OMEMO нужно будет явно включать для новых бесед. Создать ярлык Размер шрифта - Относительный размер шрифта используемый в приложении. + Относительный размер шрифта, используемый в приложении. Включено по умолчанию Выключено по умолчанию Маленький @@ -737,7 +754,7 @@ Просмотр медиафайлов Файл не прикреплен из соображений безопасности. Качество видео - Низкое качество означает меньшие файлы + Чем ниже качество, тем меньше объем файлов Среднее (360p) Высокое (720р) отменено From 8059aa7b0dfa29aa2c8971ecd7216c63adcfdbef Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Fri, 12 Jun 2020 09:40:00 +0200 Subject: [PATCH 12/29] version bump to 2.8.7-beta + changelog --- CHANGELOG.md | 5 +++++ build.gradle | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a363926b2..278897e61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +### Version 2.8.7 + +* Show help button if A/V call fails +* Fixed some annoying crashes + ### Version 2.8.6 * Offer to record voice message when callee is busy diff --git a/build.gradle b/build.gradle index 0a54ad489..ce5bbd157 100644 --- a/build.gradle +++ b/build.gradle @@ -95,8 +95,8 @@ android { defaultConfig { minSdkVersion 16 targetSdkVersion 28 - versionCode 390 - versionName "2.8.6" + versionCode 391 + versionName "2.8.7-beta" archivesBaseName += "-$versionName" applicationId "eu.siacs.conversations" resValue "string", "applicationId", applicationId From 971bb60f4288d160fd9560bdf128c4ff9b431ca0 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Fri, 12 Jun 2020 20:06:49 +0200 Subject: [PATCH 13/29] add more logging to caps fetching --- .../services/XmppConnectionService.java | 55 ++++++++++--------- 1 file changed, 30 insertions(+), 25 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index a6cc30e98..484ed3c0a 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -2778,6 +2778,7 @@ public class XmppConnectionService extends Service { updateConversationUi(); } } + private void fetchConferenceMembers(final Conversation conversation) { final Account account = conversation.getAccount(); final AxolotlService axolotlService = account.getAxolotlService(); @@ -3304,7 +3305,7 @@ public class XmppConnectionService extends Service { } public void createMessageAsync(final Message message) { - mDatabaseWriterExecutor.execute(()-> databaseBackend.createMessage(message)); + mDatabaseWriterExecutor.execute(() -> databaseBackend.createMessage(message)); } public void updateMessage(Message message, String uuid) { @@ -4444,34 +4445,38 @@ public class XmppConnectionService extends Service { public void fetchCaps(Account account, final Jid jid, final Presence presence) { final Pair key = new Pair<>(presence.getHash(), presence.getVer()); - ServiceDiscoveryResult disco = getCachedServiceDiscoveryResult(key); + final ServiceDiscoveryResult disco = getCachedServiceDiscoveryResult(key); if (disco != null) { presence.setServiceDiscoveryResult(disco); } else { - if (!account.inProgressDiscoFetches.contains(key)) { - account.inProgressDiscoFetches.add(key); - IqPacket request = new IqPacket(IqPacket.TYPE.GET); - request.setTo(jid); - final String node = presence.getNode(); - final String ver = presence.getVer(); - final Element query = request.query(Namespace.DISCO_INFO); - if (node != null && ver != null) { - query.setAttribute("node", node + "#" + ver); - } - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": making disco request for " + key.second + " to " + jid); - sendIqPacket(account, request, (a, response) -> { - if (response.getType() == IqPacket.TYPE.RESULT) { - ServiceDiscoveryResult discoveryResult = new ServiceDiscoveryResult(response); - if (presence.getVer().equals(discoveryResult.getVer())) { - databaseBackend.insertDiscoveryResult(discoveryResult); - injectServiceDiscoveryResult(a.getRoster(), presence.getHash(), presence.getVer(), discoveryResult); - } else { - Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": mismatch in caps for contact " + jid + " " + presence.getVer() + " vs " + discoveryResult.getVer()); - } - } - a.inProgressDiscoFetches.remove(key); - }); + if (account.inProgressDiscoFetches.contains(key)) { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": skipping duplicate disco request for " + key.second + " to " + jid); + return; } + account.inProgressDiscoFetches.add(key); + final IqPacket request = new IqPacket(IqPacket.TYPE.GET); + request.setTo(jid); + final String node = presence.getNode(); + final String ver = presence.getVer(); + final Element query = request.query(Namespace.DISCO_INFO); + if (node != null && ver != null) { + query.setAttribute("node", node + "#" + ver); + } + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": making disco request for " + key.second + " to " + jid); + sendIqPacket(account, request, (a, response) -> { + if (response.getType() == IqPacket.TYPE.RESULT) { + ServiceDiscoveryResult discoveryResult = new ServiceDiscoveryResult(response); + if (presence.getVer().equals(discoveryResult.getVer())) { + databaseBackend.insertDiscoveryResult(discoveryResult); + injectServiceDiscoveryResult(a.getRoster(), presence.getHash(), presence.getVer(), discoveryResult); + } else { + Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": mismatch in caps for contact " + jid + " " + presence.getVer() + " vs " + discoveryResult.getVer()); + } + } else { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": unable to fetch caps from " + jid); + } + a.inProgressDiscoFetches.remove(key); + }); } } From 56ea9647cd3b781a828748da5d493319f2c9cd34 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 13 Jun 2020 08:10:04 +0200 Subject: [PATCH 14/29] null check quicksy domain --- src/main/java/eu/siacs/conversations/entities/Conversation.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java index 1e88f8d82..85d879578 100644 --- a/src/main/java/eu/siacs/conversations/entities/Conversation.java +++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java @@ -1006,7 +1006,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl && !contact.isOwnServer() && !contact.showInContactList() && !contact.isSelf() - && !Config.QUICKSY_DOMAIN.equals(contact.getJid().toEscapedString()) + && !(Config.QUICKSY_DOMAIN != null && Config.QUICKSY_DOMAIN.equals(contact.getJid().toEscapedString())) && sentMessagesCount() == 0; } From 0dba9f560cdeb4248a287ccd5d7f6c2fcd9377bc Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 13 Jun 2020 08:26:32 +0200 Subject: [PATCH 15/29] rework quicksy domain checks --- src/main/java/eu/siacs/conversations/Config.java | 2 +- src/main/java/eu/siacs/conversations/entities/Contact.java | 2 +- .../java/eu/siacs/conversations/entities/Conversation.java | 2 +- .../siacs/conversations/services/XmppConnectionService.java | 2 +- .../eu/siacs/conversations/ui/adapter/KnownHostsAdapter.java | 2 +- src/main/java/eu/siacs/conversations/utils/JidHelper.java | 5 +++++ 6 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/Config.java b/src/main/java/eu/siacs/conversations/Config.java index f22561722..86d771740 100644 --- a/src/main/java/eu/siacs/conversations/Config.java +++ b/src/main/java/eu/siacs/conversations/Config.java @@ -41,7 +41,7 @@ public final class Config { public static final String DOMAIN_LOCK = null; //only allow account creation for this domain public static final String MAGIC_CREATE_DOMAIN = "conversations.im"; - public static final String QUICKSY_DOMAIN = "quicksy.im"; + public static final Jid QUICKSY_DOMAIN = Jid.of("quicksy.im"); public static final String CHANNEL_DISCOVERY = "https://search.jabber.network"; diff --git a/src/main/java/eu/siacs/conversations/entities/Contact.java b/src/main/java/eu/siacs/conversations/entities/Contact.java index d37646f78..7789a6686 100644 --- a/src/main/java/eu/siacs/conversations/entities/Contact.java +++ b/src/main/java/eu/siacs/conversations/entities/Contact.java @@ -134,7 +134,7 @@ public class Contact implements ListItem, Blockable { return this.systemName; } else if (!TextUtils.isEmpty(this.serverName)) { return this.serverName; - } else if (!TextUtils.isEmpty(this.presenceName) && ((QuickConversationsService.isQuicksy() && Config.QUICKSY_DOMAIN.equals(jid.getDomain().toEscapedString())) ||mutualPresenceSubscription())) { + } else if (!TextUtils.isEmpty(this.presenceName) && ((QuickConversationsService.isQuicksy() && JidHelper.isQuicksyDomain(jid.getDomain())) ||mutualPresenceSubscription())) { return this.presenceName; } else if (jid.getLocal() != null) { return JidHelper.localPartOrFallback(jid); diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java index 85d879578..83e49b9a5 100644 --- a/src/main/java/eu/siacs/conversations/entities/Conversation.java +++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java @@ -1006,7 +1006,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl && !contact.isOwnServer() && !contact.showInContactList() && !contact.isSelf() - && !(Config.QUICKSY_DOMAIN != null && Config.QUICKSY_DOMAIN.equals(contact.getJid().toEscapedString())) + && !JidHelper.isQuicksyDomain(contact.getJid()) && sentMessagesCount() == 0; } diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 484ed3c0a..48030e7f3 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -4146,7 +4146,7 @@ public class XmppConnectionService extends Service { } } if (Config.QUICKSY_DOMAIN != null) { - hosts.remove(Config.QUICKSY_DOMAIN); //we only want to show this when we type a e164 number + hosts.remove(Config.QUICKSY_DOMAIN.toEscapedString()); //we only want to show this when we type a e164 number } if (Config.DOMAIN_LOCK != null) { hosts.add(Config.DOMAIN_LOCK); diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/KnownHostsAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/KnownHostsAdapter.java index 61177d0dc..f6017cf4d 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/KnownHostsAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/KnownHostsAdapter.java @@ -27,7 +27,7 @@ public class KnownHostsAdapter extends ArrayAdapter { if (split.length == 1) { final String local = split[0].toLowerCase(Locale.ENGLISH); if (Config.QUICKSY_DOMAIN != null && E164_PATTERN.matcher(local).matches()) { - suggestions.add(local + '@' + Config.QUICKSY_DOMAIN); + suggestions.add(local + '@' + Config.QUICKSY_DOMAIN.toEscapedString()); } else { for (String domain : domains) { suggestions.add(local + '@' + domain); diff --git a/src/main/java/eu/siacs/conversations/utils/JidHelper.java b/src/main/java/eu/siacs/conversations/utils/JidHelper.java index 603899fca..c53af65ed 100644 --- a/src/main/java/eu/siacs/conversations/utils/JidHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/JidHelper.java @@ -34,6 +34,7 @@ import java.util.Arrays; import java.util.List; import java.util.Locale; +import eu.siacs.conversations.Config; import eu.siacs.conversations.xmpp.InvalidJid; import eu.siacs.conversations.xmpp.Jid; @@ -59,4 +60,8 @@ public class JidHelper { } } + public static boolean isQuicksyDomain(final Jid jid) { + return Config.QUICKSY_DOMAIN != null && Config.QUICKSY_DOMAIN.equals(jid.getDomain()); + } + } From fda9e7b51cf243658ff4fbff35ea56a630b757dc Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 13 Jun 2020 09:59:39 +0200 Subject: [PATCH 16/29] make presence selector work with empty resources (bare jid) --- .../siacs/conversations/ui/XmppActivity.java | 1788 ++++++++--------- .../ui/util/PresenceSelector.java | 11 +- 2 files changed, 902 insertions(+), 897 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java index 93196a8b0..fcdccb9c4 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java @@ -83,931 +83,927 @@ import eu.siacs.conversations.xmpp.Jid; public abstract class XmppActivity extends ActionBarActivity { - public static final String EXTRA_ACCOUNT = "account"; - protected static final int REQUEST_ANNOUNCE_PGP = 0x0101; - protected static final int REQUEST_INVITE_TO_CONVERSATION = 0x0102; - protected static final int REQUEST_CHOOSE_PGP_ID = 0x0103; - protected static final int REQUEST_BATTERY_OP = 0x49ff; - public XmppConnectionService xmppConnectionService; - public boolean xmppConnectionServiceBound = false; - - protected static final String FRAGMENT_TAG_DIALOG = "dialog"; - - private boolean isCameraFeatureAvailable = false; - - protected int mTheme; - protected boolean mUsingEnterKey = false; - protected boolean mUseTor = false; - protected Toast mToast; - public Runnable onOpenPGPKeyPublished = () -> Toast.makeText(XmppActivity.this, R.string.openpgp_has_been_published, Toast.LENGTH_SHORT).show(); - protected ConferenceInvite mPendingConferenceInvite = null; - protected ServiceConnection mConnection = new ServiceConnection() { - - @Override - public void onServiceConnected(ComponentName className, IBinder service) { - XmppConnectionBinder binder = (XmppConnectionBinder) service; - xmppConnectionService = binder.getService(); - xmppConnectionServiceBound = true; - registerListeners(); - onBackendConnected(); - } - - @Override - public void onServiceDisconnected(ComponentName arg0) { - xmppConnectionServiceBound = false; - } - }; - private DisplayMetrics metrics; - private long mLastUiRefresh = 0; - private Handler mRefreshUiHandler = new Handler(); - private Runnable mRefreshUiRunnable = () -> { - mLastUiRefresh = SystemClock.elapsedRealtime(); - refreshUiReal(); - }; - private UiCallback adhocCallback = new UiCallback() { - @Override - public void success(final Conversation conversation) { - runOnUiThread(() -> { - switchToConversation(conversation); - hideToast(); - }); - } - - @Override - public void error(final int errorCode, Conversation object) { - runOnUiThread(() -> replaceToast(getString(errorCode))); - } - - @Override - public void userInputRequired(PendingIntent pi, Conversation object) { - - } - }; - public boolean mSkipBackgroundBinding = false; - - public static boolean cancelPotentialWork(Message message, ImageView imageView) { - final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView); - - if (bitmapWorkerTask != null) { - final Message oldMessage = bitmapWorkerTask.message; - if (oldMessage == null || message != oldMessage) { - bitmapWorkerTask.cancel(true); - } else { - return false; - } - } - return true; - } - - private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) { - if (imageView != null) { - final Drawable drawable = imageView.getDrawable(); - if (drawable instanceof AsyncDrawable) { - final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable; - return asyncDrawable.getBitmapWorkerTask(); - } - } - return null; - } - - protected void hideToast() { - if (mToast != null) { - mToast.cancel(); - } - } - - protected void replaceToast(String msg) { - replaceToast(msg, true); - } - - protected void replaceToast(String msg, boolean showlong) { - hideToast(); - mToast = Toast.makeText(this, msg, showlong ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT); - mToast.show(); - } - - protected final void refreshUi() { - final long diff = SystemClock.elapsedRealtime() - mLastUiRefresh; - if (diff > Config.REFRESH_UI_INTERVAL) { - mRefreshUiHandler.removeCallbacks(mRefreshUiRunnable); - runOnUiThread(mRefreshUiRunnable); - } else { - final long next = Config.REFRESH_UI_INTERVAL - diff; - mRefreshUiHandler.removeCallbacks(mRefreshUiRunnable); - mRefreshUiHandler.postDelayed(mRefreshUiRunnable, next); - } - } - - abstract protected void refreshUiReal(); - - @Override - protected void onStart() { - super.onStart(); - if (!xmppConnectionServiceBound) { - if (this.mSkipBackgroundBinding) { - Log.d(Config.LOGTAG,"skipping background binding"); - } else { - connectToBackend(); - } - } else { - this.registerListeners(); - this.onBackendConnected(); - } - this.mUsingEnterKey = usingEnterKey(); - this.mUseTor = useTor(); - } - - public void connectToBackend() { - Intent intent = new Intent(this, XmppConnectionService.class); - intent.setAction("ui"); - try { - startService(intent); - } catch (IllegalStateException e) { - Log.w(Config.LOGTAG,"unable to start service from "+getClass().getSimpleName()); - } - bindService(intent, mConnection, Context.BIND_AUTO_CREATE); - } - - @Override - protected void onStop() { - super.onStop(); - if (xmppConnectionServiceBound) { - this.unregisterListeners(); - unbindService(mConnection); - xmppConnectionServiceBound = false; - } - } - - - public boolean hasPgp() { - return xmppConnectionService.getPgpEngine() != null; - } - - public void showInstallPgpDialog() { - Builder builder = new AlertDialog.Builder(this); - builder.setTitle(getString(R.string.openkeychain_required)); - builder.setIconAttribute(android.R.attr.alertDialogIcon); - builder.setMessage(getText(R.string.openkeychain_required_long)); - builder.setNegativeButton(getString(R.string.cancel), null); - builder.setNeutralButton(getString(R.string.restart), - (dialog, which) -> { - if (xmppConnectionServiceBound) { - unbindService(mConnection); - xmppConnectionServiceBound = false; - } - stopService(new Intent(XmppActivity.this, - XmppConnectionService.class)); - finish(); - }); - builder.setPositiveButton(getString(R.string.install), - (dialog, which) -> { - Uri uri = Uri - .parse("market://details?id=org.sufficientlysecure.keychain"); - Intent marketIntent = new Intent(Intent.ACTION_VIEW, - uri); - PackageManager manager = getApplicationContext() - .getPackageManager(); - List infos = manager - .queryIntentActivities(marketIntent, 0); - if (infos.size() > 0) { - startActivity(marketIntent); - } else { - uri = Uri.parse("http://www.openkeychain.org/"); - Intent browserIntent = new Intent( - Intent.ACTION_VIEW, uri); - startActivity(browserIntent); - } - finish(); - }); - builder.create().show(); - } - - abstract void onBackendConnected(); - - protected void registerListeners() { - if (this instanceof XmppConnectionService.OnConversationUpdate) { - this.xmppConnectionService.setOnConversationListChangedListener((XmppConnectionService.OnConversationUpdate) this); - } - if (this instanceof XmppConnectionService.OnAccountUpdate) { - this.xmppConnectionService.setOnAccountListChangedListener((XmppConnectionService.OnAccountUpdate) this); - } - if (this instanceof XmppConnectionService.OnCaptchaRequested) { - this.xmppConnectionService.setOnCaptchaRequestedListener((XmppConnectionService.OnCaptchaRequested) this); - } - if (this instanceof XmppConnectionService.OnRosterUpdate) { - this.xmppConnectionService.setOnRosterUpdateListener((XmppConnectionService.OnRosterUpdate) this); - } - if (this instanceof XmppConnectionService.OnMucRosterUpdate) { - this.xmppConnectionService.setOnMucRosterUpdateListener((XmppConnectionService.OnMucRosterUpdate) this); - } - if (this instanceof OnUpdateBlocklist) { - this.xmppConnectionService.setOnUpdateBlocklistListener((OnUpdateBlocklist) this); - } - if (this instanceof XmppConnectionService.OnShowErrorToast) { - this.xmppConnectionService.setOnShowErrorToastListener((XmppConnectionService.OnShowErrorToast) this); - } - if (this instanceof OnKeyStatusUpdated) { - this.xmppConnectionService.setOnKeyStatusUpdatedListener((OnKeyStatusUpdated) this); - } - if (this instanceof XmppConnectionService.OnJingleRtpConnectionUpdate) { - this.xmppConnectionService.setOnRtpConnectionUpdateListener((XmppConnectionService.OnJingleRtpConnectionUpdate) this); - } - } - - protected void unregisterListeners() { - if (this instanceof XmppConnectionService.OnConversationUpdate) { - this.xmppConnectionService.removeOnConversationListChangedListener((XmppConnectionService.OnConversationUpdate) this); - } - if (this instanceof XmppConnectionService.OnAccountUpdate) { - this.xmppConnectionService.removeOnAccountListChangedListener((XmppConnectionService.OnAccountUpdate) this); - } - if (this instanceof XmppConnectionService.OnCaptchaRequested) { - this.xmppConnectionService.removeOnCaptchaRequestedListener((XmppConnectionService.OnCaptchaRequested) this); - } - if (this instanceof XmppConnectionService.OnRosterUpdate) { - this.xmppConnectionService.removeOnRosterUpdateListener((XmppConnectionService.OnRosterUpdate) this); - } - if (this instanceof XmppConnectionService.OnMucRosterUpdate) { - this.xmppConnectionService.removeOnMucRosterUpdateListener((XmppConnectionService.OnMucRosterUpdate) this); - } - if (this instanceof OnUpdateBlocklist) { - this.xmppConnectionService.removeOnUpdateBlocklistListener((OnUpdateBlocklist) this); - } - if (this instanceof XmppConnectionService.OnShowErrorToast) { - this.xmppConnectionService.removeOnShowErrorToastListener((XmppConnectionService.OnShowErrorToast) this); - } - if (this instanceof OnKeyStatusUpdated) { - this.xmppConnectionService.removeOnNewKeysAvailableListener((OnKeyStatusUpdated) this); - } - if (this instanceof XmppConnectionService.OnJingleRtpConnectionUpdate) { - this.xmppConnectionService.removeRtpConnectionUpdateListener((XmppConnectionService.OnJingleRtpConnectionUpdate) this); - } - } - - @Override - public boolean onOptionsItemSelected(final MenuItem item) { - switch (item.getItemId()) { - case R.id.action_settings: - startActivity(new Intent(this, SettingsActivity.class)); - break; - case R.id.action_accounts: - AccountUtils.launchManageAccounts(this); - break; - case R.id.action_account: - AccountUtils.launchManageAccount(this); - break; - case android.R.id.home: - finish(); - break; - case R.id.action_show_qr_code: - showQrCode(); - break; - } - return super.onOptionsItemSelected(item); - } - - public void selectPresence(final Conversation conversation, final PresenceSelector.OnPresenceSelected listener) { - final Contact contact = conversation.getContact(); - if (!contact.showInRoster()) { - showAddToRosterDialog(conversation.getContact()); - } else { - final Presences presences = contact.getPresences(); - if (presences.size() == 0) { - if (!contact.getOption(Contact.Options.TO) - && !contact.getOption(Contact.Options.ASKING) - && contact.getAccount().getStatus() == Account.State.ONLINE) { - showAskForPresenceDialog(contact); - } else if (!contact.getOption(Contact.Options.TO) - || !contact.getOption(Contact.Options.FROM)) { - PresenceSelector.warnMutualPresenceSubscription(this, conversation, listener); - } else { - conversation.setNextCounterpart(null); - listener.onPresenceSelected(); - } - } else if (presences.size() == 1) { - String presence = presences.toResourceArray()[0]; - try { - conversation.setNextCounterpart(Jid.of(contact.getJid().getLocal(), contact.getJid().getDomain(), presence)); - } catch (IllegalArgumentException e) { - conversation.setNextCounterpart(null); - } - listener.onPresenceSelected(); - } else { - PresenceSelector.showPresenceSelectionDialog(this, conversation, listener); - } - } - } - - @SuppressLint("UnsupportedChromeOsCameraSystemFeature") - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - metrics = getResources().getDisplayMetrics(); - ExceptionHelper.init(getApplicationContext()); - new EmojiService(this).init(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { - this.isCameraFeatureAvailable = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY); - } else { - this.isCameraFeatureAvailable = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA); - } - this.mTheme = findTheme(); - setTheme(this.mTheme); - } - - protected boolean isCameraFeatureAvailable() { - return this.isCameraFeatureAvailable; - } - - public boolean isDarkTheme() { - return ThemeHelper.isDark(mTheme); - } - - public int getThemeResource(int r_attr_name, int r_drawable_def) { - int[] attrs = {r_attr_name}; - TypedArray ta = this.getTheme().obtainStyledAttributes(attrs); - - int res = ta.getResourceId(0, r_drawable_def); - ta.recycle(); - - return res; - } - - protected boolean isOptimizingBattery() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - final PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE); - return pm != null - && !pm.isIgnoringBatteryOptimizations(getPackageName()); - } else { - return false; - } - } - - protected boolean isAffectedByDataSaver() { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - final ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); - return cm != null - && cm.isActiveNetworkMetered() - && cm.getRestrictBackgroundStatus() == ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED; - } else { - return false; - } - } - - private boolean usingEnterKey() { - return getBooleanPreference("display_enter_key", R.bool.display_enter_key); - } - - private boolean useTor() { - return QuickConversationsService.isConversations() && getBooleanPreference("use_tor", R.bool.use_tor); - } - - protected SharedPreferences getPreferences() { - return PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); - } - - protected boolean getBooleanPreference(String name, @BoolRes int res) { - return getPreferences().getBoolean(name, getResources().getBoolean(res)); - } - - public void switchToConversation(Conversation conversation) { - switchToConversation(conversation, null); - } - - public void switchToConversationAndQuote(Conversation conversation, String text) { - switchToConversation(conversation, text, true, null, false, false); - } - - public void switchToConversation(Conversation conversation, String text) { - switchToConversation(conversation, text, false, null, false, false); - } - - public void switchToConversationDoNotAppend(Conversation conversation, String text) { - switchToConversation(conversation, text, false, null, false, true); - } - - public void highlightInMuc(Conversation conversation, String nick) { - switchToConversation(conversation, null, false, nick, false, false); - } - - public void privateMsgInMuc(Conversation conversation, String nick) { - switchToConversation(conversation, null, false, nick, true, false); - } - - private void switchToConversation(Conversation conversation, String text, boolean asQuote, String nick, boolean pm, boolean doNotAppend) { - Intent intent = new Intent(this, ConversationsActivity.class); - intent.setAction(ConversationsActivity.ACTION_VIEW_CONVERSATION); - intent.putExtra(ConversationsActivity.EXTRA_CONVERSATION, conversation.getUuid()); - if (text != null) { - intent.putExtra(Intent.EXTRA_TEXT, text); - if (asQuote) { - intent.putExtra(ConversationsActivity.EXTRA_AS_QUOTE, true); - } - } - if (nick != null) { - intent.putExtra(ConversationsActivity.EXTRA_NICK, nick); - intent.putExtra(ConversationsActivity.EXTRA_IS_PRIVATE_MESSAGE, pm); - } - if (doNotAppend) { - intent.putExtra(ConversationsActivity.EXTRA_DO_NOT_APPEND, true); - } - intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_CLEAR_TOP); - startActivity(intent); - finish(); - } - - public void switchToContactDetails(Contact contact) { - switchToContactDetails(contact, null); - } - - public void switchToContactDetails(Contact contact, String messageFingerprint) { - Intent intent = new Intent(this, ContactDetailsActivity.class); - intent.setAction(ContactDetailsActivity.ACTION_VIEW_CONTACT); - intent.putExtra(EXTRA_ACCOUNT, contact.getAccount().getJid().asBareJid().toEscapedString()); - intent.putExtra("contact", contact.getJid().toEscapedString()); - intent.putExtra("fingerprint", messageFingerprint); - startActivity(intent); - } - - public void switchToAccount(Account account, String fingerprint) { - switchToAccount(account, false, fingerprint); - } - - public void switchToAccount(Account account) { - switchToAccount(account, false, null); - } - - public void switchToAccount(Account account, boolean init, String fingerprint) { - Intent intent = new Intent(this, EditAccountActivity.class); - intent.putExtra("jid", account.getJid().asBareJid().toEscapedString()); - intent.putExtra("init", init); - if (init) { - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NO_ANIMATION); - } - if (fingerprint != null) { - intent.putExtra("fingerprint", fingerprint); - } - startActivity(intent); - if (init) { - overridePendingTransition(0, 0); - } - } - - protected void delegateUriPermissionsToService(Uri uri) { - Intent intent = new Intent(this, XmppConnectionService.class); - intent.setAction(Intent.ACTION_SEND); - intent.setData(uri); - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - try { - startService(intent); - } catch (Exception e) { - Log.e(Config.LOGTAG,"unable to delegate uri permission",e); - } - } - - protected void inviteToConversation(Conversation conversation) { - startActivityForResult(ChooseContactActivity.create(this,conversation), REQUEST_INVITE_TO_CONVERSATION); - } - - protected void announcePgp(final Account account, final Conversation conversation, Intent intent, final Runnable onSuccess) { - if (account.getPgpId() == 0) { - choosePgpSignId(account); - } else { - String status = null; - if (manuallyChangePresence()) { - status = account.getPresenceStatusMessage(); - } - if (status == null) { - status = ""; - } - xmppConnectionService.getPgpEngine().generateSignature(intent, account, status, new UiCallback() { - - @Override - public void userInputRequired(PendingIntent pi, String signature) { - try { - startIntentSenderForResult(pi.getIntentSender(), REQUEST_ANNOUNCE_PGP, null, 0, 0, 0); - } catch (final SendIntentException ignored) { - } - } - - @Override - public void success(String signature) { - account.setPgpSignature(signature); - xmppConnectionService.databaseBackend.updateAccount(account); - xmppConnectionService.sendPresence(account); - if (conversation != null) { - conversation.setNextEncryption(Message.ENCRYPTION_PGP); - xmppConnectionService.updateConversation(conversation); - refreshUi(); - } - if (onSuccess != null) { - runOnUiThread(onSuccess); - } - } - - @Override - public void error(int error, String signature) { - if (error == 0) { - account.setPgpSignId(0); - account.unsetPgpSignature(); - xmppConnectionService.databaseBackend.updateAccount(account); - choosePgpSignId(account); - } else { - displayErrorDialog(error); - } - } - }); - } - } - - @SuppressWarnings("deprecation") - @TargetApi(Build.VERSION_CODES.JELLY_BEAN) - protected void setListItemBackgroundOnView(View view) { - int sdk = android.os.Build.VERSION.SDK_INT; - if (sdk < android.os.Build.VERSION_CODES.JELLY_BEAN) { - view.setBackgroundDrawable(getResources().getDrawable(R.drawable.greybackground)); - } else { - view.setBackground(getResources().getDrawable(R.drawable.greybackground)); - } - } - - protected void choosePgpSignId(Account account) { - xmppConnectionService.getPgpEngine().chooseKey(account, new UiCallback() { - @Override - public void success(Account account1) { - } - - @Override - public void error(int errorCode, Account object) { - - } - - @Override - public void userInputRequired(PendingIntent pi, Account object) { - try { - startIntentSenderForResult(pi.getIntentSender(), - REQUEST_CHOOSE_PGP_ID, null, 0, 0, 0); - } catch (final SendIntentException ignored) { - } - } - }); - } - - protected void displayErrorDialog(final int errorCode) { - runOnUiThread(() -> { - Builder builder = new Builder(XmppActivity.this); - builder.setIconAttribute(android.R.attr.alertDialogIcon); - builder.setTitle(getString(R.string.error)); - builder.setMessage(errorCode); - builder.setNeutralButton(R.string.accept, null); - builder.create().show(); - }); - - } - - protected void showAddToRosterDialog(final Contact contact) { - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(contact.getJid().toString()); - builder.setMessage(getString(R.string.not_in_roster)); - builder.setNegativeButton(getString(R.string.cancel), null); - builder.setPositiveButton(getString(R.string.add_contact), (dialog, which) -> xmppConnectionService.createContact(contact,true)); - builder.create().show(); - } - - private void showAskForPresenceDialog(final Contact contact) { - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(contact.getJid().toString()); - builder.setMessage(R.string.request_presence_updates); - builder.setNegativeButton(R.string.cancel, null); - builder.setPositiveButton(R.string.request_now, - (dialog, which) -> { - if (xmppConnectionServiceBound) { - xmppConnectionService.sendPresencePacket(contact - .getAccount(), xmppConnectionService - .getPresenceGenerator() - .requestPresenceUpdatesFrom(contact)); - } - }); - builder.create().show(); - } - - protected void quickEdit(String previousValue, @StringRes int hint, OnValueEdited callback) { - quickEdit(previousValue, callback, hint, false, false); - } - - protected void quickEdit(String previousValue, @StringRes int hint, OnValueEdited callback, boolean permitEmpty) { - quickEdit(previousValue, callback, hint, false, permitEmpty); - } - - protected void quickPasswordEdit(String previousValue, OnValueEdited callback) { - quickEdit(previousValue, callback, R.string.password, true, false); - } - - @SuppressLint("InflateParams") - private void quickEdit(final String previousValue, - final OnValueEdited callback, - final @StringRes int hint, - boolean password, - boolean permitEmpty) { - AlertDialog.Builder builder = new AlertDialog.Builder(this); - DialogQuickeditBinding binding = DataBindingUtil.inflate(getLayoutInflater(),R.layout.dialog_quickedit, null, false); - if (password) { - binding.inputEditText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); - } - builder.setPositiveButton(R.string.accept, null); - if (hint != 0) { - binding.inputLayout.setHint(getString(hint)); - } - binding.inputEditText.requestFocus(); - if (previousValue != null) { - binding.inputEditText.getText().append(previousValue); - } - builder.setView(binding.getRoot()); - builder.setNegativeButton(R.string.cancel, null); - final AlertDialog dialog = builder.create(); - dialog.setOnShowListener(d -> SoftKeyboardUtils.showKeyboard(binding.inputEditText)); - dialog.show(); - View.OnClickListener clickListener = v -> { - String value = binding.inputEditText.getText().toString(); - if (!value.equals(previousValue) && (!value.trim().isEmpty() || permitEmpty)) { - String error = callback.onValueEdited(value); - if (error != null) { - binding.inputLayout.setError(error); - return; - } - } - SoftKeyboardUtils.hideSoftKeyboard(binding.inputEditText); - dialog.dismiss(); - }; - dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(clickListener); - dialog.getButton(DialogInterface.BUTTON_NEGATIVE).setOnClickListener((v -> { - SoftKeyboardUtils.hideSoftKeyboard(binding.inputEditText); - dialog.dismiss(); - })); - dialog.setCanceledOnTouchOutside(false); - dialog.setOnDismissListener(dialog1 -> { - SoftKeyboardUtils.hideSoftKeyboard(binding.inputEditText); + public static final String EXTRA_ACCOUNT = "account"; + protected static final int REQUEST_ANNOUNCE_PGP = 0x0101; + protected static final int REQUEST_INVITE_TO_CONVERSATION = 0x0102; + protected static final int REQUEST_CHOOSE_PGP_ID = 0x0103; + protected static final int REQUEST_BATTERY_OP = 0x49ff; + public XmppConnectionService xmppConnectionService; + public boolean xmppConnectionServiceBound = false; + + protected static final String FRAGMENT_TAG_DIALOG = "dialog"; + + private boolean isCameraFeatureAvailable = false; + + protected int mTheme; + protected boolean mUsingEnterKey = false; + protected boolean mUseTor = false; + protected Toast mToast; + public Runnable onOpenPGPKeyPublished = () -> Toast.makeText(XmppActivity.this, R.string.openpgp_has_been_published, Toast.LENGTH_SHORT).show(); + protected ConferenceInvite mPendingConferenceInvite = null; + protected ServiceConnection mConnection = new ServiceConnection() { + + @Override + public void onServiceConnected(ComponentName className, IBinder service) { + XmppConnectionBinder binder = (XmppConnectionBinder) service; + xmppConnectionService = binder.getService(); + xmppConnectionServiceBound = true; + registerListeners(); + onBackendConnected(); + } + + @Override + public void onServiceDisconnected(ComponentName arg0) { + xmppConnectionServiceBound = false; + } + }; + private DisplayMetrics metrics; + private long mLastUiRefresh = 0; + private Handler mRefreshUiHandler = new Handler(); + private Runnable mRefreshUiRunnable = () -> { + mLastUiRefresh = SystemClock.elapsedRealtime(); + refreshUiReal(); + }; + private UiCallback adhocCallback = new UiCallback() { + @Override + public void success(final Conversation conversation) { + runOnUiThread(() -> { + switchToConversation(conversation); + hideToast(); + }); + } + + @Override + public void error(final int errorCode, Conversation object) { + runOnUiThread(() -> replaceToast(getString(errorCode))); + } + + @Override + public void userInputRequired(PendingIntent pi, Conversation object) { + + } + }; + public boolean mSkipBackgroundBinding = false; + + public static boolean cancelPotentialWork(Message message, ImageView imageView) { + final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView); + + if (bitmapWorkerTask != null) { + final Message oldMessage = bitmapWorkerTask.message; + if (oldMessage == null || message != oldMessage) { + bitmapWorkerTask.cancel(true); + } else { + return false; + } + } + return true; + } + + private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) { + if (imageView != null) { + final Drawable drawable = imageView.getDrawable(); + if (drawable instanceof AsyncDrawable) { + final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable; + return asyncDrawable.getBitmapWorkerTask(); + } + } + return null; + } + + protected void hideToast() { + if (mToast != null) { + mToast.cancel(); + } + } + + protected void replaceToast(String msg) { + replaceToast(msg, true); + } + + protected void replaceToast(String msg, boolean showlong) { + hideToast(); + mToast = Toast.makeText(this, msg, showlong ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT); + mToast.show(); + } + + protected final void refreshUi() { + final long diff = SystemClock.elapsedRealtime() - mLastUiRefresh; + if (diff > Config.REFRESH_UI_INTERVAL) { + mRefreshUiHandler.removeCallbacks(mRefreshUiRunnable); + runOnUiThread(mRefreshUiRunnable); + } else { + final long next = Config.REFRESH_UI_INTERVAL - diff; + mRefreshUiHandler.removeCallbacks(mRefreshUiRunnable); + mRefreshUiHandler.postDelayed(mRefreshUiRunnable, next); + } + } + + abstract protected void refreshUiReal(); + + @Override + protected void onStart() { + super.onStart(); + if (!xmppConnectionServiceBound) { + if (this.mSkipBackgroundBinding) { + Log.d(Config.LOGTAG, "skipping background binding"); + } else { + connectToBackend(); + } + } else { + this.registerListeners(); + this.onBackendConnected(); + } + this.mUsingEnterKey = usingEnterKey(); + this.mUseTor = useTor(); + } + + public void connectToBackend() { + Intent intent = new Intent(this, XmppConnectionService.class); + intent.setAction("ui"); + try { + startService(intent); + } catch (IllegalStateException e) { + Log.w(Config.LOGTAG, "unable to start service from " + getClass().getSimpleName()); + } + bindService(intent, mConnection, Context.BIND_AUTO_CREATE); + } + + @Override + protected void onStop() { + super.onStop(); + if (xmppConnectionServiceBound) { + this.unregisterListeners(); + unbindService(mConnection); + xmppConnectionServiceBound = false; + } + } + + + public boolean hasPgp() { + return xmppConnectionService.getPgpEngine() != null; + } + + public void showInstallPgpDialog() { + Builder builder = new AlertDialog.Builder(this); + builder.setTitle(getString(R.string.openkeychain_required)); + builder.setIconAttribute(android.R.attr.alertDialogIcon); + builder.setMessage(getText(R.string.openkeychain_required_long)); + builder.setNegativeButton(getString(R.string.cancel), null); + builder.setNeutralButton(getString(R.string.restart), + (dialog, which) -> { + if (xmppConnectionServiceBound) { + unbindService(mConnection); + xmppConnectionServiceBound = false; + } + stopService(new Intent(XmppActivity.this, + XmppConnectionService.class)); + finish(); + }); + builder.setPositiveButton(getString(R.string.install), + (dialog, which) -> { + Uri uri = Uri + .parse("market://details?id=org.sufficientlysecure.keychain"); + Intent marketIntent = new Intent(Intent.ACTION_VIEW, + uri); + PackageManager manager = getApplicationContext() + .getPackageManager(); + List infos = manager + .queryIntentActivities(marketIntent, 0); + if (infos.size() > 0) { + startActivity(marketIntent); + } else { + uri = Uri.parse("http://www.openkeychain.org/"); + Intent browserIntent = new Intent( + Intent.ACTION_VIEW, uri); + startActivity(browserIntent); + } + finish(); + }); + builder.create().show(); + } + + abstract void onBackendConnected(); + + protected void registerListeners() { + if (this instanceof XmppConnectionService.OnConversationUpdate) { + this.xmppConnectionService.setOnConversationListChangedListener((XmppConnectionService.OnConversationUpdate) this); + } + if (this instanceof XmppConnectionService.OnAccountUpdate) { + this.xmppConnectionService.setOnAccountListChangedListener((XmppConnectionService.OnAccountUpdate) this); + } + if (this instanceof XmppConnectionService.OnCaptchaRequested) { + this.xmppConnectionService.setOnCaptchaRequestedListener((XmppConnectionService.OnCaptchaRequested) this); + } + if (this instanceof XmppConnectionService.OnRosterUpdate) { + this.xmppConnectionService.setOnRosterUpdateListener((XmppConnectionService.OnRosterUpdate) this); + } + if (this instanceof XmppConnectionService.OnMucRosterUpdate) { + this.xmppConnectionService.setOnMucRosterUpdateListener((XmppConnectionService.OnMucRosterUpdate) this); + } + if (this instanceof OnUpdateBlocklist) { + this.xmppConnectionService.setOnUpdateBlocklistListener((OnUpdateBlocklist) this); + } + if (this instanceof XmppConnectionService.OnShowErrorToast) { + this.xmppConnectionService.setOnShowErrorToastListener((XmppConnectionService.OnShowErrorToast) this); + } + if (this instanceof OnKeyStatusUpdated) { + this.xmppConnectionService.setOnKeyStatusUpdatedListener((OnKeyStatusUpdated) this); + } + if (this instanceof XmppConnectionService.OnJingleRtpConnectionUpdate) { + this.xmppConnectionService.setOnRtpConnectionUpdateListener((XmppConnectionService.OnJingleRtpConnectionUpdate) this); + } + } + + protected void unregisterListeners() { + if (this instanceof XmppConnectionService.OnConversationUpdate) { + this.xmppConnectionService.removeOnConversationListChangedListener((XmppConnectionService.OnConversationUpdate) this); + } + if (this instanceof XmppConnectionService.OnAccountUpdate) { + this.xmppConnectionService.removeOnAccountListChangedListener((XmppConnectionService.OnAccountUpdate) this); + } + if (this instanceof XmppConnectionService.OnCaptchaRequested) { + this.xmppConnectionService.removeOnCaptchaRequestedListener((XmppConnectionService.OnCaptchaRequested) this); + } + if (this instanceof XmppConnectionService.OnRosterUpdate) { + this.xmppConnectionService.removeOnRosterUpdateListener((XmppConnectionService.OnRosterUpdate) this); + } + if (this instanceof XmppConnectionService.OnMucRosterUpdate) { + this.xmppConnectionService.removeOnMucRosterUpdateListener((XmppConnectionService.OnMucRosterUpdate) this); + } + if (this instanceof OnUpdateBlocklist) { + this.xmppConnectionService.removeOnUpdateBlocklistListener((OnUpdateBlocklist) this); + } + if (this instanceof XmppConnectionService.OnShowErrorToast) { + this.xmppConnectionService.removeOnShowErrorToastListener((XmppConnectionService.OnShowErrorToast) this); + } + if (this instanceof OnKeyStatusUpdated) { + this.xmppConnectionService.removeOnNewKeysAvailableListener((OnKeyStatusUpdated) this); + } + if (this instanceof XmppConnectionService.OnJingleRtpConnectionUpdate) { + this.xmppConnectionService.removeRtpConnectionUpdateListener((XmppConnectionService.OnJingleRtpConnectionUpdate) this); + } + } + + @Override + public boolean onOptionsItemSelected(final MenuItem item) { + switch (item.getItemId()) { + case R.id.action_settings: + startActivity(new Intent(this, SettingsActivity.class)); + break; + case R.id.action_accounts: + AccountUtils.launchManageAccounts(this); + break; + case R.id.action_account: + AccountUtils.launchManageAccount(this); + break; + case android.R.id.home: + finish(); + break; + case R.id.action_show_qr_code: + showQrCode(); + break; + } + return super.onOptionsItemSelected(item); + } + + public void selectPresence(final Conversation conversation, final PresenceSelector.OnPresenceSelected listener) { + final Contact contact = conversation.getContact(); + if (!contact.showInRoster()) { + showAddToRosterDialog(conversation.getContact()); + } else { + final Presences presences = contact.getPresences(); + if (presences.size() == 0) { + if (!contact.getOption(Contact.Options.TO) + && !contact.getOption(Contact.Options.ASKING) + && contact.getAccount().getStatus() == Account.State.ONLINE) { + showAskForPresenceDialog(contact); + } else if (!contact.getOption(Contact.Options.TO) + || !contact.getOption(Contact.Options.FROM)) { + PresenceSelector.warnMutualPresenceSubscription(this, conversation, listener); + } else { + conversation.setNextCounterpart(null); + listener.onPresenceSelected(); + } + } else if (presences.size() == 1) { + final String presence = presences.toResourceArray()[0]; + conversation.setNextCounterpart(PresenceSelector.getNextCounterpart(contact, presence)); + listener.onPresenceSelected(); + } else { + PresenceSelector.showPresenceSelectionDialog(this, conversation, listener); + } + } + } + + @SuppressLint("UnsupportedChromeOsCameraSystemFeature") + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + metrics = getResources().getDisplayMetrics(); + ExceptionHelper.init(getApplicationContext()); + new EmojiService(this).init(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { + this.isCameraFeatureAvailable = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY); + } else { + this.isCameraFeatureAvailable = getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA); + } + this.mTheme = findTheme(); + setTheme(this.mTheme); + } + + protected boolean isCameraFeatureAvailable() { + return this.isCameraFeatureAvailable; + } + + public boolean isDarkTheme() { + return ThemeHelper.isDark(mTheme); + } + + public int getThemeResource(int r_attr_name, int r_drawable_def) { + int[] attrs = {r_attr_name}; + TypedArray ta = this.getTheme().obtainStyledAttributes(attrs); + + int res = ta.getResourceId(0, r_drawable_def); + ta.recycle(); + + return res; + } + + protected boolean isOptimizingBattery() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + final PowerManager pm = (PowerManager) getSystemService(POWER_SERVICE); + return pm != null + && !pm.isIgnoringBatteryOptimizations(getPackageName()); + } else { + return false; + } + } + + protected boolean isAffectedByDataSaver() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + final ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); + return cm != null + && cm.isActiveNetworkMetered() + && cm.getRestrictBackgroundStatus() == ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED; + } else { + return false; + } + } + + private boolean usingEnterKey() { + return getBooleanPreference("display_enter_key", R.bool.display_enter_key); + } + + private boolean useTor() { + return QuickConversationsService.isConversations() && getBooleanPreference("use_tor", R.bool.use_tor); + } + + protected SharedPreferences getPreferences() { + return PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); + } + + protected boolean getBooleanPreference(String name, @BoolRes int res) { + return getPreferences().getBoolean(name, getResources().getBoolean(res)); + } + + public void switchToConversation(Conversation conversation) { + switchToConversation(conversation, null); + } + + public void switchToConversationAndQuote(Conversation conversation, String text) { + switchToConversation(conversation, text, true, null, false, false); + } + + public void switchToConversation(Conversation conversation, String text) { + switchToConversation(conversation, text, false, null, false, false); + } + + public void switchToConversationDoNotAppend(Conversation conversation, String text) { + switchToConversation(conversation, text, false, null, false, true); + } + + public void highlightInMuc(Conversation conversation, String nick) { + switchToConversation(conversation, null, false, nick, false, false); + } + + public void privateMsgInMuc(Conversation conversation, String nick) { + switchToConversation(conversation, null, false, nick, true, false); + } + + private void switchToConversation(Conversation conversation, String text, boolean asQuote, String nick, boolean pm, boolean doNotAppend) { + Intent intent = new Intent(this, ConversationsActivity.class); + intent.setAction(ConversationsActivity.ACTION_VIEW_CONVERSATION); + intent.putExtra(ConversationsActivity.EXTRA_CONVERSATION, conversation.getUuid()); + if (text != null) { + intent.putExtra(Intent.EXTRA_TEXT, text); + if (asQuote) { + intent.putExtra(ConversationsActivity.EXTRA_AS_QUOTE, true); + } + } + if (nick != null) { + intent.putExtra(ConversationsActivity.EXTRA_NICK, nick); + intent.putExtra(ConversationsActivity.EXTRA_IS_PRIVATE_MESSAGE, pm); + } + if (doNotAppend) { + intent.putExtra(ConversationsActivity.EXTRA_DO_NOT_APPEND, true); + } + intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_CLEAR_TOP); + startActivity(intent); + finish(); + } + + public void switchToContactDetails(Contact contact) { + switchToContactDetails(contact, null); + } + + public void switchToContactDetails(Contact contact, String messageFingerprint) { + Intent intent = new Intent(this, ContactDetailsActivity.class); + intent.setAction(ContactDetailsActivity.ACTION_VIEW_CONTACT); + intent.putExtra(EXTRA_ACCOUNT, contact.getAccount().getJid().asBareJid().toEscapedString()); + intent.putExtra("contact", contact.getJid().toEscapedString()); + intent.putExtra("fingerprint", messageFingerprint); + startActivity(intent); + } + + public void switchToAccount(Account account, String fingerprint) { + switchToAccount(account, false, fingerprint); + } + + public void switchToAccount(Account account) { + switchToAccount(account, false, null); + } + + public void switchToAccount(Account account, boolean init, String fingerprint) { + Intent intent = new Intent(this, EditAccountActivity.class); + intent.putExtra("jid", account.getJid().asBareJid().toEscapedString()); + intent.putExtra("init", init); + if (init) { + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_NO_ANIMATION); + } + if (fingerprint != null) { + intent.putExtra("fingerprint", fingerprint); + } + startActivity(intent); + if (init) { + overridePendingTransition(0, 0); + } + } + + protected void delegateUriPermissionsToService(Uri uri) { + Intent intent = new Intent(this, XmppConnectionService.class); + intent.setAction(Intent.ACTION_SEND); + intent.setData(uri); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + try { + startService(intent); + } catch (Exception e) { + Log.e(Config.LOGTAG, "unable to delegate uri permission", e); + } + } + + protected void inviteToConversation(Conversation conversation) { + startActivityForResult(ChooseContactActivity.create(this, conversation), REQUEST_INVITE_TO_CONVERSATION); + } + + protected void announcePgp(final Account account, final Conversation conversation, Intent intent, final Runnable onSuccess) { + if (account.getPgpId() == 0) { + choosePgpSignId(account); + } else { + String status = null; + if (manuallyChangePresence()) { + status = account.getPresenceStatusMessage(); + } + if (status == null) { + status = ""; + } + xmppConnectionService.getPgpEngine().generateSignature(intent, account, status, new UiCallback() { + + @Override + public void userInputRequired(PendingIntent pi, String signature) { + try { + startIntentSenderForResult(pi.getIntentSender(), REQUEST_ANNOUNCE_PGP, null, 0, 0, 0); + } catch (final SendIntentException ignored) { + } + } + + @Override + public void success(String signature) { + account.setPgpSignature(signature); + xmppConnectionService.databaseBackend.updateAccount(account); + xmppConnectionService.sendPresence(account); + if (conversation != null) { + conversation.setNextEncryption(Message.ENCRYPTION_PGP); + xmppConnectionService.updateConversation(conversation); + refreshUi(); + } + if (onSuccess != null) { + runOnUiThread(onSuccess); + } + } + + @Override + public void error(int error, String signature) { + if (error == 0) { + account.setPgpSignId(0); + account.unsetPgpSignature(); + xmppConnectionService.databaseBackend.updateAccount(account); + choosePgpSignId(account); + } else { + displayErrorDialog(error); + } + } + }); + } + } + + @SuppressWarnings("deprecation") + @TargetApi(Build.VERSION_CODES.JELLY_BEAN) + protected void setListItemBackgroundOnView(View view) { + int sdk = android.os.Build.VERSION.SDK_INT; + if (sdk < android.os.Build.VERSION_CODES.JELLY_BEAN) { + view.setBackgroundDrawable(getResources().getDrawable(R.drawable.greybackground)); + } else { + view.setBackground(getResources().getDrawable(R.drawable.greybackground)); + } + } + + protected void choosePgpSignId(Account account) { + xmppConnectionService.getPgpEngine().chooseKey(account, new UiCallback() { + @Override + public void success(Account account1) { + } + + @Override + public void error(int errorCode, Account object) { + + } + + @Override + public void userInputRequired(PendingIntent pi, Account object) { + try { + startIntentSenderForResult(pi.getIntentSender(), + REQUEST_CHOOSE_PGP_ID, null, 0, 0, 0); + } catch (final SendIntentException ignored) { + } + } }); - } + } - protected boolean hasStoragePermission(int requestCode) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { - requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, requestCode); - return false; - } else { - return true; - } - } else { - return true; - } - } + protected void displayErrorDialog(final int errorCode) { + runOnUiThread(() -> { + Builder builder = new Builder(XmppActivity.this); + builder.setIconAttribute(android.R.attr.alertDialogIcon); + builder.setTitle(getString(R.string.error)); + builder.setMessage(errorCode); + builder.setNeutralButton(R.string.accept, null); + builder.create().show(); + }); - protected void onActivityResult(int requestCode, int resultCode, final Intent data) { - super.onActivityResult(requestCode, resultCode, data); - if (requestCode == REQUEST_INVITE_TO_CONVERSATION && resultCode == RESULT_OK) { - mPendingConferenceInvite = ConferenceInvite.parse(data); - if (xmppConnectionServiceBound && mPendingConferenceInvite != null) { - if (mPendingConferenceInvite.execute(this)) { - mToast = Toast.makeText(this, R.string.creating_conference, Toast.LENGTH_LONG); - mToast.show(); - } - mPendingConferenceInvite = null; - } - } - } + } - public boolean copyTextToClipboard(String text, int labelResId) { - ClipboardManager mClipBoardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); - String label = getResources().getString(labelResId); - if (mClipBoardManager != null) { - ClipData mClipData = ClipData.newPlainText(label, text); - mClipBoardManager.setPrimaryClip(mClipData); - return true; - } - return false; - } + protected void showAddToRosterDialog(final Contact contact) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(contact.getJid().toString()); + builder.setMessage(getString(R.string.not_in_roster)); + builder.setNegativeButton(getString(R.string.cancel), null); + builder.setPositiveButton(getString(R.string.add_contact), (dialog, which) -> xmppConnectionService.createContact(contact, true)); + builder.create().show(); + } - protected boolean manuallyChangePresence() { - return getBooleanPreference(SettingsActivity.MANUALLY_CHANGE_PRESENCE, R.bool.manually_change_presence); - } + private void showAskForPresenceDialog(final Contact contact) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(contact.getJid().toString()); + builder.setMessage(R.string.request_presence_updates); + builder.setNegativeButton(R.string.cancel, null); + builder.setPositiveButton(R.string.request_now, + (dialog, which) -> { + if (xmppConnectionServiceBound) { + xmppConnectionService.sendPresencePacket(contact + .getAccount(), xmppConnectionService + .getPresenceGenerator() + .requestPresenceUpdatesFrom(contact)); + } + }); + builder.create().show(); + } - protected String getShareableUri() { - return getShareableUri(false); - } + protected void quickEdit(String previousValue, @StringRes int hint, OnValueEdited callback) { + quickEdit(previousValue, callback, hint, false, false); + } - protected String getShareableUri(boolean http) { - return null; - } + protected void quickEdit(String previousValue, @StringRes int hint, OnValueEdited callback, boolean permitEmpty) { + quickEdit(previousValue, callback, hint, false, permitEmpty); + } - protected void shareLink(boolean http) { - String uri = getShareableUri(http); - if (uri == null || uri.isEmpty()) { - return; - } - Intent intent = new Intent(Intent.ACTION_SEND); - intent.setType("text/plain"); - intent.putExtra(Intent.EXTRA_TEXT, getShareableUri(http)); - try { - startActivity(Intent.createChooser(intent, getText(R.string.share_uri_with))); - } catch (ActivityNotFoundException e) { - Toast.makeText(this, R.string.no_application_to_share_uri, Toast.LENGTH_SHORT).show(); - } - } + protected void quickPasswordEdit(String previousValue, OnValueEdited callback) { + quickEdit(previousValue, callback, R.string.password, true, false); + } - protected void launchOpenKeyChain(long keyId) { - PgpEngine pgp = XmppActivity.this.xmppConnectionService.getPgpEngine(); - try { - startIntentSenderForResult( - pgp.getIntentForKey(keyId).getIntentSender(), 0, null, 0, - 0, 0); - } catch (Throwable e) { - Toast.makeText(XmppActivity.this, R.string.openpgp_error, Toast.LENGTH_SHORT).show(); - } - } + @SuppressLint("InflateParams") + private void quickEdit(final String previousValue, + final OnValueEdited callback, + final @StringRes int hint, + boolean password, + boolean permitEmpty) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + DialogQuickeditBinding binding = DataBindingUtil.inflate(getLayoutInflater(), R.layout.dialog_quickedit, null, false); + if (password) { + binding.inputEditText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); + } + builder.setPositiveButton(R.string.accept, null); + if (hint != 0) { + binding.inputLayout.setHint(getString(hint)); + } + binding.inputEditText.requestFocus(); + if (previousValue != null) { + binding.inputEditText.getText().append(previousValue); + } + builder.setView(binding.getRoot()); + builder.setNegativeButton(R.string.cancel, null); + final AlertDialog dialog = builder.create(); + dialog.setOnShowListener(d -> SoftKeyboardUtils.showKeyboard(binding.inputEditText)); + dialog.show(); + View.OnClickListener clickListener = v -> { + String value = binding.inputEditText.getText().toString(); + if (!value.equals(previousValue) && (!value.trim().isEmpty() || permitEmpty)) { + String error = callback.onValueEdited(value); + if (error != null) { + binding.inputLayout.setError(error); + return; + } + } + SoftKeyboardUtils.hideSoftKeyboard(binding.inputEditText); + dialog.dismiss(); + }; + dialog.getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener(clickListener); + dialog.getButton(DialogInterface.BUTTON_NEGATIVE).setOnClickListener((v -> { + SoftKeyboardUtils.hideSoftKeyboard(binding.inputEditText); + dialog.dismiss(); + })); + dialog.setCanceledOnTouchOutside(false); + dialog.setOnDismissListener(dialog1 -> { + SoftKeyboardUtils.hideSoftKeyboard(binding.inputEditText); + }); + } - @Override - public void onResume() { - super.onResume(); - } + protected boolean hasStoragePermission(int requestCode) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + if (checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { + requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, requestCode); + return false; + } else { + return true; + } + } else { + return true; + } + } - protected int findTheme() { - return ThemeHelper.find(this); - } + protected void onActivityResult(int requestCode, int resultCode, final Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == REQUEST_INVITE_TO_CONVERSATION && resultCode == RESULT_OK) { + mPendingConferenceInvite = ConferenceInvite.parse(data); + if (xmppConnectionServiceBound && mPendingConferenceInvite != null) { + if (mPendingConferenceInvite.execute(this)) { + mToast = Toast.makeText(this, R.string.creating_conference, Toast.LENGTH_LONG); + mToast.show(); + } + mPendingConferenceInvite = null; + } + } + } - @Override - public void onPause() { - super.onPause(); - } + public boolean copyTextToClipboard(String text, int labelResId) { + ClipboardManager mClipBoardManager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); + String label = getResources().getString(labelResId); + if (mClipBoardManager != null) { + ClipData mClipData = ClipData.newPlainText(label, text); + mClipBoardManager.setPrimaryClip(mClipData); + return true; + } + return false; + } - @Override - public boolean onMenuOpened(int id, Menu menu) { - if(id == AppCompatDelegate.FEATURE_SUPPORT_ACTION_BAR && menu != null) { - MenuDoubleTabUtil.recordMenuOpen(); - } - return super.onMenuOpened(id, menu); - } + protected boolean manuallyChangePresence() { + return getBooleanPreference(SettingsActivity.MANUALLY_CHANGE_PRESENCE, R.bool.manually_change_presence); + } - protected void showQrCode() { - showQrCode(getShareableUri()); - } + protected String getShareableUri() { + return getShareableUri(false); + } - protected void showQrCode(final String uri) { - if (uri == null || uri.isEmpty()) { - return; - } - Point size = new Point(); - getWindowManager().getDefaultDisplay().getSize(size); - final int width = (size.x < size.y ? size.x : size.y); - Bitmap bitmap = BarcodeProvider.create2dBarcodeBitmap(uri, width); - ImageView view = new ImageView(this); - view.setBackgroundColor(Color.WHITE); - view.setImageBitmap(bitmap); - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setView(view); - builder.create().show(); - } + protected String getShareableUri(boolean http) { + return null; + } - protected Account extractAccount(Intent intent) { - final String jid = intent != null ? intent.getStringExtra(EXTRA_ACCOUNT) : null; - try { - return jid != null ? xmppConnectionService.findAccountByJid(Jid.ofEscaped(jid)) : null; - } catch (IllegalArgumentException e) { - return null; - } - } + protected void shareLink(boolean http) { + String uri = getShareableUri(http); + if (uri == null || uri.isEmpty()) { + return; + } + Intent intent = new Intent(Intent.ACTION_SEND); + intent.setType("text/plain"); + intent.putExtra(Intent.EXTRA_TEXT, getShareableUri(http)); + try { + startActivity(Intent.createChooser(intent, getText(R.string.share_uri_with))); + } catch (ActivityNotFoundException e) { + Toast.makeText(this, R.string.no_application_to_share_uri, Toast.LENGTH_SHORT).show(); + } + } - public AvatarService avatarService() { - return xmppConnectionService.getAvatarService(); - } + protected void launchOpenKeyChain(long keyId) { + PgpEngine pgp = XmppActivity.this.xmppConnectionService.getPgpEngine(); + try { + startIntentSenderForResult( + pgp.getIntentForKey(keyId).getIntentSender(), 0, null, 0, + 0, 0); + } catch (Throwable e) { + Toast.makeText(XmppActivity.this, R.string.openpgp_error, Toast.LENGTH_SHORT).show(); + } + } - public void loadBitmap(Message message, ImageView imageView) { - Bitmap bm; - try { - bm = xmppConnectionService.getFileBackend().getThumbnail(message, (int) (metrics.density * 288), true); - } catch (IOException e) { - bm = null; - } - if (bm != null) { - cancelPotentialWork(message, imageView); - imageView.setImageBitmap(bm); - imageView.setBackgroundColor(0x00000000); - } else { - if (cancelPotentialWork(message, imageView)) { - imageView.setBackgroundColor(0xff333333); - imageView.setImageDrawable(null); - final BitmapWorkerTask task = new BitmapWorkerTask(imageView); - final AsyncDrawable asyncDrawable = new AsyncDrawable( - getResources(), null, task); - imageView.setImageDrawable(asyncDrawable); - try { - task.execute(message); - } catch (final RejectedExecutionException ignored) { - ignored.printStackTrace(); - } - } - } - } + @Override + public void onResume() { + super.onResume(); + } - protected interface OnValueEdited { - String onValueEdited(String value); - } + protected int findTheme() { + return ThemeHelper.find(this); + } - public static class ConferenceInvite { - private String uuid; - private List jids = new ArrayList<>(); + @Override + public void onPause() { + super.onPause(); + } - public static ConferenceInvite parse(Intent data) { - ConferenceInvite invite = new ConferenceInvite(); - invite.uuid = data.getStringExtra(ChooseContactActivity.EXTRA_CONVERSATION); - if (invite.uuid == null) { - return null; - } - invite.jids.addAll(ChooseContactActivity.extractJabberIds(data)); - return invite; - } + @Override + public boolean onMenuOpened(int id, Menu menu) { + if (id == AppCompatDelegate.FEATURE_SUPPORT_ACTION_BAR && menu != null) { + MenuDoubleTabUtil.recordMenuOpen(); + } + return super.onMenuOpened(id, menu); + } - public boolean execute(XmppActivity activity) { - XmppConnectionService service = activity.xmppConnectionService; - Conversation conversation = service.findConversationByUuid(this.uuid); - if (conversation == null) { - return false; - } - if (conversation.getMode() == Conversation.MODE_MULTI) { - for (Jid jid : jids) { - service.invite(conversation, jid); - } - return false; - } else { - jids.add(conversation.getJid().asBareJid()); - return service.createAdhocConference(conversation.getAccount(), null, jids, activity.adhocCallback); - } - } - } + protected void showQrCode() { + showQrCode(getShareableUri()); + } - static class BitmapWorkerTask extends AsyncTask { - private final WeakReference imageViewReference; - private Message message = null; + protected void showQrCode(final String uri) { + if (uri == null || uri.isEmpty()) { + return; + } + Point size = new Point(); + getWindowManager().getDefaultDisplay().getSize(size); + final int width = (size.x < size.y ? size.x : size.y); + Bitmap bitmap = BarcodeProvider.create2dBarcodeBitmap(uri, width); + ImageView view = new ImageView(this); + view.setBackgroundColor(Color.WHITE); + view.setImageBitmap(bitmap); + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setView(view); + builder.create().show(); + } - private BitmapWorkerTask(ImageView imageView) { - this.imageViewReference = new WeakReference<>(imageView); - } + protected Account extractAccount(Intent intent) { + final String jid = intent != null ? intent.getStringExtra(EXTRA_ACCOUNT) : null; + try { + return jid != null ? xmppConnectionService.findAccountByJid(Jid.ofEscaped(jid)) : null; + } catch (IllegalArgumentException e) { + return null; + } + } - @Override - protected Bitmap doInBackground(Message... params) { - if (isCancelled()) { - return null; - } - message = params[0]; - try { - final XmppActivity activity = find(imageViewReference); - if (activity != null && activity.xmppConnectionService != null) { - return activity.xmppConnectionService.getFileBackend().getThumbnail(message, (int) (activity.metrics.density * 288), false); - } else { - return null; - } - } catch (IOException e) { - return null; - } - } + public AvatarService avatarService() { + return xmppConnectionService.getAvatarService(); + } - @Override - protected void onPostExecute(final Bitmap bitmap) { - if (!isCancelled()) { - final ImageView imageView = imageViewReference.get(); - if (imageView != null) { - imageView.setImageBitmap(bitmap); - imageView.setBackgroundColor(bitmap == null ? 0xff333333 : 0x00000000); - } - } - } - } + public void loadBitmap(Message message, ImageView imageView) { + Bitmap bm; + try { + bm = xmppConnectionService.getFileBackend().getThumbnail(message, (int) (metrics.density * 288), true); + } catch (IOException e) { + bm = null; + } + if (bm != null) { + cancelPotentialWork(message, imageView); + imageView.setImageBitmap(bm); + imageView.setBackgroundColor(0x00000000); + } else { + if (cancelPotentialWork(message, imageView)) { + imageView.setBackgroundColor(0xff333333); + imageView.setImageDrawable(null); + final BitmapWorkerTask task = new BitmapWorkerTask(imageView); + final AsyncDrawable asyncDrawable = new AsyncDrawable( + getResources(), null, task); + imageView.setImageDrawable(asyncDrawable); + try { + task.execute(message); + } catch (final RejectedExecutionException ignored) { + ignored.printStackTrace(); + } + } + } + } - private static class AsyncDrawable extends BitmapDrawable { - private final WeakReference bitmapWorkerTaskReference; + protected interface OnValueEdited { + String onValueEdited(String value); + } - private AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) { - super(res, bitmap); - bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask); - } + public static class ConferenceInvite { + private String uuid; + private List jids = new ArrayList<>(); - private BitmapWorkerTask getBitmapWorkerTask() { - return bitmapWorkerTaskReference.get(); - } - } + public static ConferenceInvite parse(Intent data) { + ConferenceInvite invite = new ConferenceInvite(); + invite.uuid = data.getStringExtra(ChooseContactActivity.EXTRA_CONVERSATION); + if (invite.uuid == null) { + return null; + } + invite.jids.addAll(ChooseContactActivity.extractJabberIds(data)); + return invite; + } - public static XmppActivity find(@NonNull WeakReference viewWeakReference) { - final View view = viewWeakReference.get(); - return view == null ? null : find(view); - } + public boolean execute(XmppActivity activity) { + XmppConnectionService service = activity.xmppConnectionService; + Conversation conversation = service.findConversationByUuid(this.uuid); + if (conversation == null) { + return false; + } + if (conversation.getMode() == Conversation.MODE_MULTI) { + for (Jid jid : jids) { + service.invite(conversation, jid); + } + return false; + } else { + jids.add(conversation.getJid().asBareJid()); + return service.createAdhocConference(conversation.getAccount(), null, jids, activity.adhocCallback); + } + } + } - public static XmppActivity find(@NonNull final View view) { - Context context = view.getContext(); - while (context instanceof ContextWrapper) { - if (context instanceof XmppActivity) { - return (XmppActivity) context; - } - context = ((ContextWrapper)context).getBaseContext(); - } - return null; - } + static class BitmapWorkerTask extends AsyncTask { + private final WeakReference imageViewReference; + private Message message = null; + + private BitmapWorkerTask(ImageView imageView) { + this.imageViewReference = new WeakReference<>(imageView); + } + + @Override + protected Bitmap doInBackground(Message... params) { + if (isCancelled()) { + return null; + } + message = params[0]; + try { + final XmppActivity activity = find(imageViewReference); + if (activity != null && activity.xmppConnectionService != null) { + return activity.xmppConnectionService.getFileBackend().getThumbnail(message, (int) (activity.metrics.density * 288), false); + } else { + return null; + } + } catch (IOException e) { + return null; + } + } + + @Override + protected void onPostExecute(final Bitmap bitmap) { + if (!isCancelled()) { + final ImageView imageView = imageViewReference.get(); + if (imageView != null) { + imageView.setImageBitmap(bitmap); + imageView.setBackgroundColor(bitmap == null ? 0xff333333 : 0x00000000); + } + } + } + } + + private static class AsyncDrawable extends BitmapDrawable { + private final WeakReference bitmapWorkerTaskReference; + + private AsyncDrawable(Resources res, Bitmap bitmap, BitmapWorkerTask bitmapWorkerTask) { + super(res, bitmap); + bitmapWorkerTaskReference = new WeakReference<>(bitmapWorkerTask); + } + + private BitmapWorkerTask getBitmapWorkerTask() { + return bitmapWorkerTaskReference.get(); + } + } + + public static XmppActivity find(@NonNull WeakReference viewWeakReference) { + final View view = viewWeakReference.get(); + return view == null ? null : find(view); + } + + public static XmppActivity find(@NonNull final View view) { + Context context = view.getContext(); + while (context instanceof ContextWrapper) { + if (context instanceof XmppActivity) { + return (XmppActivity) context; + } + context = ((ContextWrapper) context).getBaseContext(); + } + return null; + } } diff --git a/src/main/java/eu/siacs/conversations/ui/util/PresenceSelector.java b/src/main/java/eu/siacs/conversations/ui/util/PresenceSelector.java index 14e82b406..870fdc268 100644 --- a/src/main/java/eu/siacs/conversations/ui/util/PresenceSelector.java +++ b/src/main/java/eu/siacs/conversations/ui/util/PresenceSelector.java @@ -106,12 +106,21 @@ public class PresenceSelector { builder.setPositiveButton( R.string.ok, (dialog, which) -> onFullJidSelected.onFullJidSelected( - Jid.of(contact.getJid().getLocal(), contact.getJid().getDomain(), resourceArray[selectedResource.get()]) + getNextCounterpart(contact, resourceArray[selectedResource.get()]) ) ); builder.create().show(); } + + public static Jid getNextCounterpart(final Contact contact, final String resource) { + if (resource.isEmpty()) { + return contact.getJid().asBareJid(); + } else { + return contact.getJid().withResource(resource); + } + } + public static void warnMutualPresenceSubscription(Activity activity, final Conversation conversation, final OnPresenceSelected listener) { AlertDialog.Builder builder = new AlertDialog.Builder(activity); builder.setTitle(conversation.getContact().getJid().toString()); From 400c8461fc1fb9a76a91b2ffffba9c533684534a Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 13 Jun 2020 22:53:24 +0200 Subject: [PATCH 17/29] fix feature discovery in jingle file transfer for empty resources --- .../siacs/conversations/entities/Message.java | 18 +++++++----------- .../ui/util/PresenceSelector.java | 9 ++++++--- .../jingle/JingleFileTransferConnection.java | 14 +++++--------- 3 files changed, 18 insertions(+), 23 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java index 4263a6ef8..44b8db691 100644 --- a/src/main/java/eu/siacs/conversations/entities/Message.java +++ b/src/main/java/eu/siacs/conversations/entities/Message.java @@ -6,6 +6,8 @@ import android.graphics.Color; import android.text.SpannableStringBuilder; import android.util.Log; +import com.google.common.base.Strings; + import org.json.JSONException; import java.lang.ref.WeakReference; @@ -21,6 +23,7 @@ import java.util.Set; import eu.siacs.conversations.Config; import eu.siacs.conversations.crypto.axolotl.FingerprintStatus; import eu.siacs.conversations.services.AvatarService; +import eu.siacs.conversations.ui.util.PresenceSelector; import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.Emoticons; import eu.siacs.conversations.utils.GeoHelper; @@ -745,19 +748,12 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable } public boolean fixCounterpart() { - Presences presences = conversation.getContact().getPresences(); - if (counterpart != null && presences.has(counterpart.getResource())) { + final Presences presences = conversation.getContact().getPresences(); + if (counterpart != null && presences.has(Strings.nullToEmpty(counterpart.getResource()))) { return true; } else if (presences.size() >= 1) { - try { - counterpart = Jid.of(conversation.getJid().getLocal(), - conversation.getJid().getDomain(), - presences.toResourceArray()[0]); - return true; - } catch (IllegalArgumentException e) { - counterpart = null; - return false; - } + counterpart = PresenceSelector.getNextCounterpart(getContact(),presences.toResourceArray()[0]); + return true; } else { counterpart = null; return false; diff --git a/src/main/java/eu/siacs/conversations/ui/util/PresenceSelector.java b/src/main/java/eu/siacs/conversations/ui/util/PresenceSelector.java index 870fdc268..2f29e5b76 100644 --- a/src/main/java/eu/siacs/conversations/ui/util/PresenceSelector.java +++ b/src/main/java/eu/siacs/conversations/ui/util/PresenceSelector.java @@ -112,12 +112,15 @@ public class PresenceSelector { builder.create().show(); } - public static Jid getNextCounterpart(final Contact contact, final String resource) { + return getNextCounterpart(contact.getJid(), resource); + } + + public static Jid getNextCounterpart(final Jid jid, final String resource) { if (resource.isEmpty()) { - return contact.getJid().asBareJid(); + return jid.asBareJid(); } else { - return contact.getJid().withResource(resource); + return jid.withResource(resource); } } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleFileTransferConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleFileTransferConnection.java index 36948d48b..0cab56279 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleFileTransferConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleFileTransferConnection.java @@ -4,6 +4,7 @@ import android.util.Base64; import android.util.Log; import com.google.common.base.Preconditions; +import com.google.common.base.Strings; import com.google.common.collect.Collections2; import com.google.common.collect.FluentIterable; import com.google.common.collect.Iterables; @@ -416,15 +417,10 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple } private List getRemoteFeatures() { - final Jid jid = this.id.with; - String resource = jid != null ? jid.getResource() : null; - if (resource != null) { - Presence presence = this.id.account.getRoster().getContact(jid).getPresences().get(resource); - ServiceDiscoveryResult result = presence != null ? presence.getServiceDiscoveryResult() : null; - return result == null ? Collections.emptyList() : result.getFeatures(); - } else { - return Collections.emptyList(); - } + final String resource = Strings.nullToEmpty(this.id.with.getResource()); + final Presence presence = this.id.account.getRoster().getContact(id.with).getPresences().get(resource); + final ServiceDiscoveryResult result = presence != null ? presence.getServiceDiscoveryResult() : null; + return result == null ? Collections.emptyList() : result.getFeatures(); } private void init(JinglePacket packet) { //should move to deliverPacket From ccdc91a4974dee9aa98c2a11906aaf6d97e24056 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 14 Jun 2020 09:01:47 +0200 Subject: [PATCH 18/29] remove check that would ensure you use jingle only with full jids --- .../xmpp/jingle/AbstractJingleConnection.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/AbstractJingleConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/AbstractJingleConnection.java index 679a30abe..bd50a22a2 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/AbstractJingleConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/AbstractJingleConnection.java @@ -16,10 +16,10 @@ public abstract class AbstractJingleConnection { public static final String JINGLE_MESSAGE_PROPOSE_ID_PREFIX = "jm-propose-"; public static final String JINGLE_MESSAGE_PROCEED_ID_PREFIX = "jm-proceed-"; - protected final JingleConnectionManager jingleConnectionManager; + final JingleConnectionManager jingleConnectionManager; protected final XmppConnectionService xmppConnectionService; protected final Id id; - protected final Jid initiator; + private final Jid initiator; AbstractJingleConnection(final JingleConnectionManager jingleConnectionManager, final Id id, final Jid initiator) { this.jingleConnectionManager = jingleConnectionManager; @@ -47,8 +47,9 @@ public abstract class AbstractJingleConnection { public final String sessionId; private Id(final Account account, final Jid with, final String sessionId) { + Preconditions.checkNotNull(account); Preconditions.checkNotNull(with); - Preconditions.checkArgument(with.isFullJid()); + Preconditions.checkNotNull(sessionId); this.account = account; this.with = with; this.sessionId = sessionId; From c3b9a4dabcbc24e96e429ee4533e0e164ce48e50 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 14 Jun 2020 10:34:40 +0200 Subject: [PATCH 19/29] parse jmi proposals from MAM reloads. fixes #3778 --- .../conversations/parser/MessageParser.java | 40 ++++++++++++++++--- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/parser/MessageParser.java b/src/main/java/eu/siacs/conversations/parser/MessageParser.java index 37cf7ed33..991fea547 100644 --- a/src/main/java/eu/siacs/conversations/parser/MessageParser.java +++ b/src/main/java/eu/siacs/conversations/parser/MessageParser.java @@ -836,6 +836,10 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece for (Element child : packet.getChildren()) { if (Namespace.JINGLE_MESSAGE.equals(child.getNamespace()) && JINGLE_MESSAGE_ELEMENT_NAMES.contains(child.getName())) { final String action = child.getName(); + final String sessionId = child.getAttribute("id"); + if (sessionId == null) { + break; + } if (query == null) { if (serverMsgId == null) { serverMsgId = extractStanzaId(account, packet); @@ -845,10 +849,6 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece processMessageReceipts(account, packet, query); } } else if (query.isCatchup()) { - final String sessionId = child.getAttribute("id"); - if (sessionId == null) { - break; - } if ("propose".equals(action)) { final Element description = child.findChild("description"); final String namespace = description == null ? null : description.getNamespace(); @@ -872,7 +872,6 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece c.add(message); mXmppConnectionService.databaseBackend.createMessage(message); } - } else if ("proceed".equals(action)) { //status needs to be flipped to find the original propose final Conversation c = mXmppConnectionService.findOrCreateConversation(account, counterpart.asBareJid(), false, false); @@ -890,6 +889,37 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece } } + } else { + //MAM reloads (non catchups + if ("propose".equals(action)) { + final Element description = child.findChild("description"); + final String namespace = description == null ? null : description.getNamespace(); + if (Namespace.JINGLE_APPS_RTP.equals(namespace)) { + final Conversation c = mXmppConnectionService.findOrCreateConversation(account, counterpart.asBareJid(), false, false); + final Message preExistingMessage = c.findRtpSession(sessionId, status); + if (preExistingMessage != null) { + preExistingMessage.setServerMsgId(serverMsgId); + mXmppConnectionService.updateMessage(preExistingMessage); + break; + } + final Message message = new Message( + c, + status, + Message.TYPE_RTP_SESSION, + sessionId + ); + message.setServerMsgId(serverMsgId); + message.setTime(timestamp); + message.setBody(new RtpSessionStatus(true, 0).toString()); + if (query.getPagingOrder() == MessageArchiveService.PagingOrder.REVERSE) { + c.prepend(query.getActualInThisQuery(), message); + } else { + c.add(message); + } + query.incrementActualMessageCount(); + mXmppConnectionService.databaseBackend.createMessage(message); + } + } } break; } From 16bc210211bd8c31e1466bb3a13a4b976d852eb6 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 14 Jun 2020 11:28:25 +0200 Subject: [PATCH 20/29] fix regression for fixed domain --- src/main/java/eu/siacs/conversations/utils/AccountUtils.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/eu/siacs/conversations/utils/AccountUtils.java b/src/main/java/eu/siacs/conversations/utils/AccountUtils.java index 09b65f694..a324b242e 100644 --- a/src/main/java/eu/siacs/conversations/utils/AccountUtils.java +++ b/src/main/java/eu/siacs/conversations/utils/AccountUtils.java @@ -39,7 +39,7 @@ public class AccountUtils { for (Account account : service.getAccounts()) { if (account.getStatus() != Account.State.DISABLED) { if (Config.DOMAIN_LOCK != null) { - accounts.add(account.getJid().toEscapedString()); + accounts.add(account.getJid().getEscapedLocal()); } else { accounts.add(account.getJid().asBareJid().toEscapedString()); } From 61aac78af0c4b84df09b0ac9e41c0a9fa00d3cba Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 14 Jun 2020 14:29:40 +0200 Subject: [PATCH 21/29] pulled translations from transifex --- src/main/res/values-de/strings.xml | 2 ++ src/main/res/values-es/strings.xml | 2 ++ src/main/res/values-gl/strings.xml | 2 ++ src/main/res/values-pl/strings.xml | 2 ++ src/main/res/values-ro-rRO/strings.xml | 2 ++ 5 files changed, 10 insertions(+) diff --git a/src/main/res/values-de/strings.xml b/src/main/res/values-de/strings.xml index f35175a36..e6dccaa8d 100644 --- a/src/main/res/values-de/strings.xml +++ b/src/main/res/values-de/strings.xml @@ -901,6 +901,7 @@ Klingelt Besetzt Verbindungsaufbau fehlgeschlagen + Verbindung unterbrochen Anruf zurückgenommen App-Fehler Auflegen @@ -914,6 +915,7 @@ Entgangener Anruf Audioanruf Videoanruf + Hilfe Dein Mikrofon ist nicht verfügbar Du kannst immer nur einen Anruf zur gleichen Zeit machen. Zurück zum laufenden Aufruf diff --git a/src/main/res/values-es/strings.xml b/src/main/res/values-es/strings.xml index adff37f82..7bada1588 100644 --- a/src/main/res/values-es/strings.xml +++ b/src/main/res/values-es/strings.xml @@ -901,6 +901,7 @@ Llamando Ocupado No se ha podido realizar la llamada + Conexión perdida Llamada rechazada Fallo en la aplicación Colgar @@ -914,6 +915,7 @@ Llamada perdida Audio llamada Video llamada + Ayuda Tu micrófono no está disponible Solo puedes hacer una llamada a la vez Volver a la llamada en curso diff --git a/src/main/res/values-gl/strings.xml b/src/main/res/values-gl/strings.xml index c11373e9e..50b85e6bb 100644 --- a/src/main/res/values-gl/strings.xml +++ b/src/main/res/values-gl/strings.xml @@ -901,6 +901,7 @@ Sonando Ocupado Non se pode establecer a chamada + Perdeuse a conexión Chamada cortada Fallo na aplicación Colgar @@ -914,6 +915,7 @@ Chamada perdida Chamada de audio Chamada de vídeo + Axuda O micrófono non está dispoñible Só podes manter unha chamada en cada momento. Voltar á chamada activa diff --git a/src/main/res/values-pl/strings.xml b/src/main/res/values-pl/strings.xml index 119f063bf..6ade662c9 100644 --- a/src/main/res/values-pl/strings.xml +++ b/src/main/res/values-pl/strings.xml @@ -919,6 +919,7 @@ Administrator twojego serwera będzie mógł czytać twoje wiadomości, ale moż Dzwonienie Zajęty Nie można wykonać połączenia + Utracono połączenie Anulowane połączenie Błąd aplikacji Rozłącz @@ -932,6 +933,7 @@ Administrator twojego serwera będzie mógł czytać twoje wiadomości, ale moż Nieodebrane połączenie Połączenie audio Połączenie wideo + Pomoc Twój mikrofon jest niedostępny Możesz mieć tylko jedno połączenie na raz. Powróć do trwającego połączenia diff --git a/src/main/res/values-ro-rRO/strings.xml b/src/main/res/values-ro-rRO/strings.xml index bd12f4b79..b25555ea3 100644 --- a/src/main/res/values-ro-rRO/strings.xml +++ b/src/main/res/values-ro-rRO/strings.xml @@ -909,6 +909,7 @@ Sună Ocupat Nu s-a putut conecta apelul + Conexiune pierdută Apel anulat Eroare de aplicație Închide @@ -922,6 +923,7 @@ Apel pierdut Apel audio Apel video + Ajutor Microfonul nu este disponibil Puteți avea un singur apel simultan. Reveniți la apelul în curs From 1f77d5e1152976321a3bdbe8843992f8cb061bb1 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 14 Jun 2020 16:22:53 +0200 Subject: [PATCH 22/29] pass selected audio device to proximity sensor toggle --- .../eu/siacs/conversations/ui/RtpSessionActivity.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java index a11f5b053..23a743eda 100644 --- a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java @@ -280,9 +280,9 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe this.mProximityWakeLock = null; } } - - private void putProximityWakeLockInProperState() { - if (requireRtpConnection().getAudioManager().getSelectedAudioDevice() == AppRTCAudioManager.AudioDevice.EARPIECE) { + + private void putProximityWakeLockInProperState(final AppRTCAudioManager.AudioDevice audioDevice) { + if (audioDevice == AppRTCAudioManager.AudioDevice.EARPIECE) { acquireProximityWakeLock(); } else { releaseProximityWakeLock(); @@ -1037,7 +1037,7 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe } else if (END_CARD.contains(endUserState)) { Log.d(Config.LOGTAG, "onAudioDeviceChanged() nothing to do because end card has been reached"); } else { - putProximityWakeLockInProperState(); + putProximityWakeLockInProperState(selectedAudioDevice); } } catch (IllegalStateException e) { Log.d(Config.LOGTAG, "RTP connection was not available when audio device changed"); From 20286ea8d2a1917fe2316b729c005aac6a210483 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 14 Jun 2020 19:09:55 +0200 Subject: [PATCH 23/29] fixed concurrent modification when displaying read markers --- src/main/java/eu/siacs/conversations/entities/Message.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java index 44b8db691..44f7588f8 100644 --- a/src/main/java/eu/siacs/conversations/entities/Message.java +++ b/src/main/java/eu/siacs/conversations/entities/Message.java @@ -7,6 +7,7 @@ import android.text.SpannableStringBuilder; import android.util.Log; import com.google.common.base.Strings; +import com.google.common.collect.ImmutableSet; import org.json.JSONException; @@ -534,7 +535,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable } public Set getReadByMarkers() { - return Collections.unmodifiableSet(this.readByMarkers); + return ImmutableSet.copyOf(this.readByMarkers); } boolean similar(Message message) { From 38b2764c81ccd91cb348f8b9aa66d508ece7b28d Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 14 Jun 2020 19:11:03 +0200 Subject: [PATCH 24/29] version bump to 2.8.7-beta.2 --- CHANGELOG.md | 1 + build.gradle | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 278897e61..ce7f4430e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ * Show help button if A/V call fails * Fixed some annoying crashes +* Fixed Jingle connections (file transfer + calls) with bare JIDs ### Version 2.8.6 diff --git a/build.gradle b/build.gradle index ce5bbd157..de399f4f9 100644 --- a/build.gradle +++ b/build.gradle @@ -95,8 +95,8 @@ android { defaultConfig { minSdkVersion 16 targetSdkVersion 28 - versionCode 391 - versionName "2.8.7-beta" + versionCode 392 + versionName "2.8.7-beta.2" archivesBaseName += "-$versionName" applicationId "eu.siacs.conversations" resValue "string", "applicationId", applicationId From d823cefe47109571e73932a276c0e2fbeed5f699 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Mon, 15 Jun 2020 12:40:43 +0200 Subject: [PATCH 25/29] pulled translations from transifex --- src/main/res/values-de/strings.xml | 2 +- src/main/res/values-it/strings.xml | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/res/values-de/strings.xml b/src/main/res/values-de/strings.xml index e6dccaa8d..eea90eeac 100644 --- a/src/main/res/values-de/strings.xml +++ b/src/main/res/values-de/strings.xml @@ -41,7 +41,7 @@ Moderator Teilnehmer Besucher - Möchtest du %svon deiner Kontaktliste entfernen? Unterhaltungen mit diesem Kontakt werden dabei nicht entfernt. + Möchtest du %s von deiner Kontaktliste entfernen? Unterhaltungen mit diesem Kontakt werden dabei nicht entfernt. Möchtest du %s sperren und keine Nachrichten mehr erhalten? Möchtest du %s entsperren und wieder Nachrichten empfangen? Alle Kontakte von %s sperren? diff --git a/src/main/res/values-it/strings.xml b/src/main/res/values-it/strings.xml index 526e662d2..c2d1d7d88 100644 --- a/src/main/res/values-it/strings.xml +++ b/src/main/res/values-it/strings.xml @@ -901,6 +901,7 @@ Sta squillando Occupato Impossibile connettere la chiamata + Connessione persa Chiamata ritirata Errore dell\'app Riaggancia @@ -914,6 +915,7 @@ Chiamata persa Chiamata vocale Chiamata video + Aiuto Il tuo microfono non è disponibile Puoi fare solo una chiamata alla volta. Torna alla chiamata in corso From 98e1044bddde162e47944d1c969884cdf14a0d98 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Mon, 15 Jun 2020 19:17:21 +0200 Subject: [PATCH 26/29] use Jid.ofLocalAndDomainEscaped when transforming account with locked domain --- .../eu/siacs/conversations/ui/ChannelDiscoveryActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/eu/siacs/conversations/ui/ChannelDiscoveryActivity.java b/src/main/java/eu/siacs/conversations/ui/ChannelDiscoveryActivity.java index c0b97759d..398be7f68 100644 --- a/src/main/java/eu/siacs/conversations/ui/ChannelDiscoveryActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ChannelDiscoveryActivity.java @@ -263,7 +263,7 @@ public class ChannelDiscoveryActivity extends XmppActivity implements MenuItem.O } public void joinChannelSearchResult(String selectedAccount, Room result) { - final Jid jid = Config.DOMAIN_LOCK == null ? Jid.ofEscaped(selectedAccount) : Jid.ofEscaped(selectedAccount, Config.DOMAIN_LOCK, null); + final Jid jid = Config.DOMAIN_LOCK == null ? Jid.ofEscaped(selectedAccount) : Jid.ofLocalAndDomainEscaped(selectedAccount, Config.DOMAIN_LOCK); final boolean syncAutoJoin = getBooleanPreference("autojoin", R.bool.autojoin); final Account account = xmppConnectionService.findAccountByJid(jid); final Conversation conversation = xmppConnectionService.findOrCreateConversation(account, result.getRoom(), true, true, true); From c8f23aef4edc0d4da82eb0e9ed95ebc7b88e64bf Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Mon, 15 Jun 2020 21:33:32 +0200 Subject: [PATCH 27/29] error response to sending the jingle ft hash should not file the transfer --- .../jingle/JingleFileTransferConnection.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleFileTransferConnection.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleFileTransferConnection.java index 0cab56279..408635f93 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleFileTransferConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleFileTransferConnection.java @@ -200,7 +200,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple } }; - public JingleFileTransferConnection(JingleConnectionManager jingleConnectionManager, Id id, Jid initiator) { + JingleFileTransferConnection(JingleConnectionManager jingleConnectionManager, Id id, Jid initiator) { super(jingleConnectionManager, id, initiator); } @@ -630,10 +630,14 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple final JinglePacket packet = this.bootstrapPacket(JinglePacket.Action.SESSION_INFO); packet.addJingleChild(checksum); - this.sendJinglePacket(packet); + xmppConnectionService.sendIqPacket(id.account, packet, (account, response) -> { + if (response.getType() == IqPacket.TYPE.ERROR) { + Log.d(Config.LOGTAG,account.getJid().asBareJid()+": ignoring error response to our session-info (hash transmission)"); + } + }); } - public Collection getOurCandidates() { + private Collection getOurCandidates() { return Collections2.filter(this.candidates, c -> c != null && c.isOurs()); } @@ -1019,7 +1023,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple abort(Reason.CANCEL); } - void abort(final Reason reason) { + private void abort(final Reason reason) { this.disconnectSocks5Connections(); if (this.transport instanceof JingleInBandTransport) { this.transport.disconnect(); @@ -1163,7 +1167,7 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple } } - public int getJingleStatus() { + private int getJingleStatus() { return this.mJingleStatus; } @@ -1206,11 +1210,11 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple jingleConnectionManager.updateConversationUi(false); } - public String getTransportId() { + String getTransportId() { return this.transportId; } - public FileTransferDescription.Version getFtVersion() { + FileTransferDescription.Version getFtVersion() { return this.description.getVersion(); } From 23c4a264045c27212a7a76eca7a2a248d29416f3 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 16 Jun 2020 10:16:25 +0200 Subject: [PATCH 28/29] pulled translations from transifex --- src/main/res/values-sv/strings.xml | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/main/res/values-sv/strings.xml b/src/main/res/values-sv/strings.xml index cf936ef56..affc4780d 100644 --- a/src/main/res/values-sv/strings.xml +++ b/src/main/res/values-sv/strings.xml @@ -3,6 +3,7 @@ Inställningar Ny konversation Kontoinställningar + Hantera konto Stäng denna konversation Kontaktdetaljer Gruppchattdetaljer @@ -34,18 +35,20 @@ Avkrypterar meddelande. Vänta… OpenPGP-krypterat meddelande Nick används redan - Ogiltigt nick + Ogiltigt smeknamn Admin Ägare Moderator Deltagare Besökare + Vill du ta bort %s från din kontaktlista? Konversationer med denna kontakt kommer inte tas bort. Vill du blockera %s från att skicka dig meddelanden? Vill du avblockera %s och tillåta denne att skicka dig meddelanden? Blockera alla kontakter från %s? Avblockera alla kontakter från %s? Kontakt blockerad Blockerad + Vill du ta bort %s som ett bokmärke? Konversationer med detta bokmärke kommer inte tas bort. Registrera nytt konto på servern Byt lösenord på server Dela med… @@ -66,9 +69,13 @@ Conversations har kraschat Skicka nu Fråga aldrig igen + Kunde inte ansluta till konto + Kunde inte ansluta till flera konton Bifoga fil Lägg till kontakt sändning misslyckades + Förbereder att skicka bild + Förbereder att skicka bilder Delar filer. Vänta... Rensa historik Rensa konversationshistorik @@ -81,6 +88,7 @@ Skicka OMEMO-krypterat meddelande Skicka v\\OMEMO-krypterat meddelande Skicka OpenPGP-krypterat meddelande + Nytt smeknamn används Skicka okrypterat Avkryptering misslyckades. Du har kanske kanske inte rätt privat nyckel. OpenKeychain @@ -304,6 +312,7 @@ Bannlys nu Kunde inte ändra rollen för %s Privat, medlemsskap krävs + Gör XMPP-adresser synliga för alla Du deltar ej Aldrig Tills vidare @@ -389,6 +398,8 @@ %d meddelanden Ladda fler meddelanden + Ge Conversations tillgång till extern lagring + Ge Conversations tillgång till kameran Synkronisera med kontakter Notifiera för alla meddelanden Notifieringar deaktiverade @@ -442,6 +453,7 @@ Tillåt Saknar rättigheter för access till %s Fjärrserver hittas inte + Kunde inte uppdatera konto Ta bort OMEMO identiteter Ta bort valda nycklar Du måste vara ansluten för att publicera din avatarbild @@ -451,6 +463,7 @@ Din enhet stödjer inte att deaktivera databesparing för Conversations. Denna enhet har verifierats Kopiera fingeravtryck + Streckkoden innehåller inte fingeravtryck för denna konversation. Verifierade fingeravtryck Använd kameran för att scanna en kontakts streckkod Vänta medans nycklar hämtas @@ -554,6 +567,7 @@ e-bok Öppna med... Välj konto + Ange ditt lösenord till kontot %s för att återställa säkerhetskopian. Skapa gruppchatt Skapa sluten gruppchatt Kanalnamn @@ -576,5 +590,7 @@ Detta verkar vara ett domännamn Lägg till ändå Detta ser ut som en kanaladress + Filen du valde är inte en säkerhetskopia till Conversations Om + Aktivera ett konto From 9649ba16b5bf24709e342eff0af820f24c1771db Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 16 Jun 2020 10:22:48 +0200 Subject: [PATCH 29/29] version bump to 2.8.7 --- build.gradle | 4 ++-- fastlane/metadata/android/en-US/changelogs/394.txt | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/394.txt diff --git a/build.gradle b/build.gradle index de399f4f9..eef177a04 100644 --- a/build.gradle +++ b/build.gradle @@ -95,8 +95,8 @@ android { defaultConfig { minSdkVersion 16 targetSdkVersion 28 - versionCode 392 - versionName "2.8.7-beta.2" + versionCode 393 + versionName "2.8.7" archivesBaseName += "-$versionName" applicationId "eu.siacs.conversations" resValue "string", "applicationId", applicationId diff --git a/fastlane/metadata/android/en-US/changelogs/394.txt b/fastlane/metadata/android/en-US/changelogs/394.txt new file mode 100644 index 000000000..82250ee87 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/394.txt @@ -0,0 +1,3 @@ +* Show help button if A/V call fails +* Fixed some annoying crashes +* Fixed Jingle connections (file transfer + calls) with bare JIDs