From 905489e23703848f78f27f7f6bf742a7b80014b3 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 4 May 2021 09:47:09 +0200 Subject: [PATCH 01/82] bump firebase-messaging libray version --- build.gradle | 2 +- .../siacs/conversations/services/PushManagementService.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index f30385970..f21de848c 100644 --- a/build.gradle +++ b/build.gradle @@ -35,7 +35,7 @@ configurations { dependencies { implementation 'androidx.viewpager:viewpager:1.0.0' - playstoreImplementation('com.google.firebase:firebase-messaging:21.0.1') { + playstoreImplementation('com.google.firebase:firebase-messaging:21.1.0') { exclude group: 'com.google.firebase', module: 'firebase-core' exclude group: 'com.google.firebase', module: 'firebase-analytics' exclude group: 'com.google.firebase', module: 'firebase-measurement-connector' diff --git a/src/playstore/java/eu/siacs/conversations/services/PushManagementService.java b/src/playstore/java/eu/siacs/conversations/services/PushManagementService.java index db1ad6586..45180b5d5 100644 --- a/src/playstore/java/eu/siacs/conversations/services/PushManagementService.java +++ b/src/playstore/java/eu/siacs/conversations/services/PushManagementService.java @@ -3,7 +3,7 @@ package eu.siacs.conversations.services; import android.util.Log; import com.google.android.gms.common.ConnectionResult; -import com.google.android.gms.common.GoogleApiAvailability; +import com.google.android.gms.common.GoogleApiAvailabilityLight; import com.google.firebase.messaging.FirebaseMessaging; import eu.siacs.conversations.Config; @@ -107,7 +107,7 @@ public class PushManagementService { } private boolean playServicesAvailable() { - return GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(mXmppConnectionService) == ConnectionResult.SUCCESS; + return GoogleApiAvailabilityLight.getInstance().isGooglePlayServicesAvailable(mXmppConnectionService) == ConnectionResult.SUCCESS; } public boolean isStub() { From 48156dd27f44adfbb12b2321fd22d671fb7e3ca6 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 4 May 2021 10:10:30 +0200 Subject: [PATCH 02/82] a/v calls: seperate out SECURITY error from APP_FAILURE until now problems with verifying the call (omemo or DTLS missing) would just be another app failure. This commit displays verifications problems as their own thing. --- .../conversations/ui/RtpSessionActivity.java | 16 +++++++++++--- .../xmpp/jingle/AbstractJingleConnection.java | 3 ++- .../xmpp/jingle/JingleRtpConnection.java | 22 ++++++++++++++----- .../xmpp/jingle/RtpEndUserState.java | 3 ++- src/main/res/values/strings.xml | 1 + 5 files changed, 34 insertions(+), 11 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java index 496244300..e9e12d7e3 100644 --- a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java @@ -81,6 +81,7 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe private static final List END_CARD = Arrays.asList( RtpEndUserState.APPLICATION_ERROR, + RtpEndUserState.SECURITY_ERROR, RtpEndUserState.DECLINED_OR_BUSY, RtpEndUserState.CONNECTIVITY_ERROR, RtpEndUserState.CONNECTIVITY_LOST_ERROR, @@ -88,7 +89,8 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe ); private static final List STATES_SHOWING_HELP_BUTTON = Arrays.asList( RtpEndUserState.APPLICATION_ERROR, - RtpEndUserState.CONNECTIVITY_ERROR + RtpEndUserState.CONNECTIVITY_ERROR, + RtpEndUserState.SECURITY_ERROR ); private static final List STATES_SHOWING_SWITCH_TO_CHAT = Arrays.asList( RtpEndUserState.CONNECTING, @@ -668,6 +670,9 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe case APPLICATION_ERROR: setTitle(R.string.rtp_state_application_failure); break; + case SECURITY_ERROR: + setTitle(R.string.rtp_state_security_error); + break; case ENDED: throw new IllegalStateException("Activity should have called finishAndReleaseWakeLock();"); default: @@ -743,7 +748,8 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe RtpEndUserState.CONNECTIVITY_ERROR, RtpEndUserState.CONNECTIVITY_LOST_ERROR, RtpEndUserState.APPLICATION_ERROR, - RtpEndUserState.RETRACTED + RtpEndUserState.RETRACTED, + RtpEndUserState.SECURITY_ERROR ).contains(state)) { this.binding.rejectCall.setContentDescription(getString(R.string.exit)); this.binding.rejectCall.setOnClickListener(this::exit); @@ -928,7 +934,11 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe if (isPictureInPicture()) { binding.appBarLayout.setVisibility(View.GONE); binding.pipPlaceholder.setVisibility(View.VISIBLE); - if (state == RtpEndUserState.APPLICATION_ERROR || state == RtpEndUserState.CONNECTIVITY_ERROR) { + if (Arrays.asList( + RtpEndUserState.APPLICATION_ERROR, + RtpEndUserState.CONNECTIVITY_ERROR, + RtpEndUserState.SECURITY_ERROR) + .contains(state)) { binding.pipWarning.setVisibility(View.VISIBLE); binding.pipWaiting.setVisibility(View.GONE); } else { 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 fcacd35d2..90f06fe26 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/AbstractJingleConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/AbstractJingleConnection.java @@ -136,6 +136,7 @@ public abstract class AbstractJingleConnection { TERMINATED_DECLINED_OR_BUSY, //equal to 'ENDED' (after other party declined the call) TERMINATED_CONNECTIVITY_ERROR, //equal to 'ENDED' (but after network failures; ui will display retry button) TERMINATED_CANCEL_OR_TIMEOUT, //more or less the same as retracted; caller pressed end call before session was accepted - TERMINATED_APPLICATION_FAILURE + TERMINATED_APPLICATION_FAILURE, + TERMINATED_SECURITY_ERROR } } 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 1f9eeb3c4..80133268e 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -71,7 +71,8 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web State.TERMINATED_DECLINED_OR_BUSY, State.TERMINATED_CONNECTIVITY_ERROR, State.TERMINATED_CANCEL_OR_TIMEOUT, - State.TERMINATED_APPLICATION_FAILURE + State.TERMINATED_APPLICATION_FAILURE, + State.TERMINATED_SECURITY_ERROR ); private static final Map> VALID_TRANSITIONS; @@ -81,7 +82,8 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web transitionBuilder.put(State.NULL, ImmutableList.of( State.PROPOSED, State.SESSION_INITIALIZED, - State.TERMINATED_APPLICATION_FAILURE + State.TERMINATED_APPLICATION_FAILURE, + State.TERMINATED_SECURITY_ERROR )); transitionBuilder.put(State.PROPOSED, ImmutableList.of( State.ACCEPTED, @@ -89,6 +91,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web State.REJECTED, State.RETRACTED, State.TERMINATED_APPLICATION_FAILURE, + State.TERMINATED_SECURITY_ERROR, State.TERMINATED_CONNECTIVITY_ERROR //only used when the xmpp connection rebinds )); transitionBuilder.put(State.PROCEED, ImmutableList.of( @@ -97,6 +100,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web State.SESSION_INITIALIZED_PRE_APPROVED, State.TERMINATED_SUCCESS, State.TERMINATED_APPLICATION_FAILURE, + State.TERMINATED_SECURITY_ERROR, State.TERMINATED_CONNECTIVITY_ERROR //at this state used for error bounces of the proceed message )); transitionBuilder.put(State.SESSION_INITIALIZED, ImmutableList.of( @@ -105,7 +109,8 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web State.TERMINATED_DECLINED_OR_BUSY, State.TERMINATED_CONNECTIVITY_ERROR, //at this state used for IQ errors and IQ timeouts State.TERMINATED_CANCEL_OR_TIMEOUT, - State.TERMINATED_APPLICATION_FAILURE + State.TERMINATED_APPLICATION_FAILURE, + State.TERMINATED_SECURITY_ERROR )); transitionBuilder.put(State.SESSION_INITIALIZED_PRE_APPROVED, ImmutableList.of( State.SESSION_ACCEPTED, @@ -113,14 +118,16 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web State.TERMINATED_DECLINED_OR_BUSY, State.TERMINATED_CONNECTIVITY_ERROR, //at this state used for IQ errors and IQ timeouts State.TERMINATED_CANCEL_OR_TIMEOUT, - State.TERMINATED_APPLICATION_FAILURE + State.TERMINATED_APPLICATION_FAILURE, + State.TERMINATED_SECURITY_ERROR )); transitionBuilder.put(State.SESSION_ACCEPTED, ImmutableList.of( State.TERMINATED_SUCCESS, State.TERMINATED_DECLINED_OR_BUSY, State.TERMINATED_CONNECTIVITY_ERROR, State.TERMINATED_CANCEL_OR_TIMEOUT, - State.TERMINATED_APPLICATION_FAILURE + State.TERMINATED_APPLICATION_FAILURE, + State.TERMINATED_SECURITY_ERROR )); VALID_TRANSITIONS = transitionBuilder.build(); } @@ -164,8 +171,9 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web case CANCEL: case TIMEOUT: return State.TERMINATED_CANCEL_OR_TIMEOUT; - case FAILED_APPLICATION: case SECURITY_ERROR: + return State.TERMINATED_SECURITY_ERROR; + case FAILED_APPLICATION: case UNSUPPORTED_TRANSPORTS: case UNSUPPORTED_APPLICATIONS: return State.TERMINATED_APPLICATION_FAILURE; @@ -959,6 +967,8 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web return rtpConnectionStarted == 0 ? RtpEndUserState.CONNECTIVITY_ERROR : RtpEndUserState.CONNECTIVITY_LOST_ERROR; case TERMINATED_APPLICATION_FAILURE: return RtpEndUserState.APPLICATION_ERROR; + case TERMINATED_SECURITY_ERROR: + return RtpEndUserState.SECURITY_ERROR; } throw new IllegalStateException(String.format("%s has no equivalent EndUserState", this.state)); } 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 3b97fcbc7..61536bb7c 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpEndUserState.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/RtpEndUserState.java @@ -13,5 +13,6 @@ public enum RtpEndUserState { 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 + APPLICATION_ERROR, //something rather bad happened; libwebrtc failed or we got in IQ-error + SECURITY_ERROR //problem with DTLS (missing) or verification } diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 2db8b1da3..c9ad3d891 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -912,6 +912,7 @@ Connection lost Retracted call App failure + Verification problem Hang up Ongoing call Ongoing video call From 3b25fb9038348d9dce186569084820beec491247 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 4 May 2021 10:49:41 +0200 Subject: [PATCH 03/82] encrypt to inactive and untrusted devices in jingle encrypting to untrusted devices means no degradition of security compared to not encrypting at all. Trust status display (shield) is made independently at a later stage. --- .../eu/siacs/conversations/crypto/axolotl/AxolotlService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java index 6f233d871..e89eb4d13 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -1217,7 +1217,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(account.getJid().asBareJid(), getOwnDeviceId()); final String content = child.getContent(); axolotlMessage.encrypt(content); - axolotlMessage.addDevice(session); + axolotlMessage.addDevice(session, true); fingerprint.addChild(axolotlMessage.toElement()); transportInfo.addChild(fingerprint); } else { From 9544b994dc91948feaa45abe663134a37180a29a Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 4 May 2021 17:52:17 +0200 Subject: [PATCH 04/82] invoke omemo trust/fetch activity when triggering phone call in require_verification mode --- .../java/eu/siacs/conversations/Config.java | 1 + .../ui/ConversationFragment.java | 21 ++++++++++++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/Config.java b/src/main/java/eu/siacs/conversations/Config.java index 7f7662bd1..5286abf49 100644 --- a/src/main/java/eu/siacs/conversations/Config.java +++ b/src/main/java/eu/siacs/conversations/Config.java @@ -119,6 +119,7 @@ public final class Config { public static final boolean ENCRYPT_ON_HTTP_UPLOADED = false; public static final boolean X509_VERIFICATION = false; //use x509 certificates to verify OMEMO keys + public static final boolean REQUIRE_RTP_VERIFICATION = false; //require a/v calls to be verified with OMEMO public static final boolean ONLY_INTERNAL_STORAGE = false; //use internal storage instead of sdcard to save attachments diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index 31105cac5..37d7024e0 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -734,7 +734,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke if (body.length() == 0 || conversation == null) { return; } - if (conversation.getNextEncryption() == Message.ENCRYPTION_AXOLOTL && trustKeysIfNeeded(REQUEST_TRUST_KEYS_TEXT)) { + if (trustKeysIfNeeded(conversation, REQUEST_TRUST_KEYS_TEXT)) { return; } final Message message; @@ -757,6 +757,10 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke } } + private boolean trustKeysIfNeeded(final Conversation conversation, final int requestCode) { + return conversation.getNextEncryption() == Message.ENCRYPTION_AXOLOTL && trustKeysIfNeeded(requestCode); + } + protected boolean trustKeysIfNeeded(int requestCode) { AxolotlService axolotlService = conversation.getAccount().getAxolotlService(); final List targets = axolotlService.getCryptoTargets(conversation); @@ -824,6 +828,12 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke case REQUEST_TRUST_KEYS_ATTACHMENTS: commitAttachments(); break; + case REQUEST_START_AUDIO_CALL: + triggerRtpSession(RtpSessionActivity.ACTION_MAKE_VOICE_CALL); + break; + case REQUEST_START_VIDEO_CALL: + triggerRtpSession(RtpSessionActivity.ACTION_MAKE_VIDEO_CALL); + break; case ATTACHMENT_CHOICE_CHOOSE_IMAGE: final List imageUris = Attachment.extractAttachments(getActivity(), data, Attachment.Type.IMAGE); mediaPreviewAdapter.addMediaPreviews(imageUris); @@ -870,7 +880,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke if (anyNeedsExternalStoragePermission(attachments) && !hasPermissions(REQUEST_COMMIT_ATTACHMENTS, Manifest.permission.WRITE_EXTERNAL_STORAGE)) { return; } - if (conversation.getNextEncryption() == Message.ENCRYPTION_AXOLOTL && trustKeysIfNeeded(REQUEST_TRUST_KEYS_ATTACHMENTS)) { + if (trustKeysIfNeeded(conversation, REQUEST_TRUST_KEYS_ATTACHMENTS)) { return; } final PresenceSelector.OnPresenceSelected callback = () -> { @@ -1345,6 +1355,9 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke return; } if (hasPermissions(REQUEST_START_AUDIO_CALL, Manifest.permission.RECORD_AUDIO)) { + if (Config.REQUIRE_RTP_VERIFICATION && trustKeysIfNeeded(conversation, REQUEST_START_AUDIO_CALL)) { + return; + } triggerRtpSession(RtpSessionActivity.ACTION_MAKE_VOICE_CALL); } } @@ -1355,6 +1368,9 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke return; } if (hasPermissions(REQUEST_START_VIDEO_CALL, Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA)) { + if (Config.REQUIRE_RTP_VERIFICATION && trustKeysIfNeeded(conversation, REQUEST_START_VIDEO_CALL)) { + return; + } triggerRtpSession(RtpSessionActivity.ACTION_MAKE_VIDEO_CALL); } } @@ -1365,7 +1381,6 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke Toast.makeText(getActivity(), R.string.only_one_call_at_a_time, Toast.LENGTH_LONG).show(); return; } - final Contact contact = conversation.getContact(); if (contact.getPresences().anySupport(Namespace.JINGLE_MESSAGE)) { triggerRtpSession(contact.getAccount(), contact.getJid().asBareJid(), action); From e2324209ed7efd00eb4220c5877f9a1110fcd932 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 4 May 2021 19:04:01 +0200 Subject: [PATCH 05/82] make sure omemo sessions are verified if the the respective config flag is set --- .../crypto/axolotl/AxolotlService.java | 24 +++++++++++++++++++ .../xmpp/jingle/JingleRtpConnection.java | 21 +++++++++------- .../xmpp/jingle/stanzas/Reason.java | 10 ++++++++ 3 files changed, 46 insertions(+), 9 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java index e89eb4d13..c2d111e4b 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -1234,6 +1234,9 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { if (session == null) { throw new CryptoFailedException(String.format("No session found for %d", deviceId)); } + if (Config.REQUIRE_RTP_VERIFICATION) { + requireVerification(session); + } final ImmutableMap.Builder descriptionTransportBuilder = new ImmutableMap.Builder<>(); final OmemoVerification omemoVerification = new OmemoVerification(); omemoVerification.setDeviceId(deviceId); @@ -1283,6 +1286,9 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { final Element encrypted = child.findChildEnsureSingle(XmppAxolotlMessage.CONTAINERTAG, AxolotlService.PEP_PREFIX); final XmppAxolotlMessage xmppAxolotlMessage = XmppAxolotlMessage.fromElement(encrypted, from.asBareJid()); final XmppAxolotlSession session = getReceivingSession(xmppAxolotlMessage); + if (Config.REQUIRE_RTP_VERIFICATION) { + requireVerification(session); + } final XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintext = xmppAxolotlMessage.decrypt(session, getOwnDeviceId()); final Integer preKeyId = session.getPreKeyIdAndReset(); if (preKeyId != null) { @@ -1299,6 +1305,16 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { return new OmemoVerifiedPayload<>(omemoVerification, transportInfo); } + private static void requireVerification(final XmppAxolotlSession session) { + if (session.getTrust().isVerified()) { + return; + } + throw new NotVerifiedException(String.format( + "session with %s was not verified", + session.getFingerprint() + )); + } + public void prepareKeyTransportMessage(final Conversation conversation, final OnMessageCreatedCallback onMessageCreatedCallback) { executor.execute(new Runnable() { @Override @@ -1690,4 +1706,12 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { return payload; } } + + public static class NotVerifiedException extends SecurityException { + + public NotVerifiedException(String message) { + super(message); + } + + } } 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 80133268e..3fea3fee6 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -762,9 +762,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web } catch (final WebRTCWrapper.InitializationException e) { Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": unable to initialize WebRTC"); webRTCWrapper.close(); - sendJingleMessage("retract", id.with.asBareJid()); - transitionOrThrow(State.TERMINATED_APPLICATION_FAILURE); - this.finish(); + sendRetract(Reason.ofException(e)); return; } try { @@ -776,22 +774,27 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web } catch (final Exception e) { Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": unable to sendSessionInitiate", Throwables.getRootCause(e)); webRTCWrapper.close(); + final Reason reason = Reason.ofException(e); if (isInState(targetState)) { - sendSessionTerminate(Reason.FAILED_APPLICATION); + sendSessionTerminate(reason); } else { - sendJingleMessage("retract", id.with.asBareJid()); - transitionOrThrow(State.TERMINATED_APPLICATION_FAILURE); - this.finish(); + sendRetract(reason); } } } + private void sendRetract(final Reason reason) { + //TODO embed reason into retract + sendJingleMessage("retract", id.with.asBareJid()); + transitionOrThrow(reasonToState(reason)); + this.finish(); + } + private void sendSessionInitiate(final RtpContentMap rtpContentMap, final State targetState) { this.initiatorRtpContentMap = rtpContentMap; - this.transitionOrThrow(targetState); - //TODO do on background thread? final RtpContentMap outgoingContentMap = encryptSessionInitiate(rtpContentMap); final JinglePacket sessionInitiate = outgoingContentMap.toJinglePacket(JinglePacket.Action.SESSION_INITIATE, id.sessionId); + this.transitionOrThrow(targetState); send(sessionInitiate); } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/Reason.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/Reason.java index c419045b0..365fda070 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/Reason.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/Reason.java @@ -3,7 +3,9 @@ package eu.siacs.conversations.xmpp.jingle.stanzas; import androidx.annotation.NonNull; import com.google.common.base.CaseFormat; +import com.google.common.base.Throwables; +import eu.siacs.conversations.crypto.axolotl.AxolotlService; import eu.siacs.conversations.xmpp.jingle.RtpContentMap; public enum Reason { @@ -51,4 +53,12 @@ public enum Reason { return FAILED_APPLICATION; } } + + public static Reason ofException(final Exception e) { + final Throwable root = Throwables.getRootCause(e); + if (root instanceof RuntimeException) { + return of((RuntimeException) root); + } + return FAILED_APPLICATION; + } } \ No newline at end of file From 9c16af25fb653cb32e9d6f035d0320d2b522f7a3 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 6 May 2021 17:57:06 +0200 Subject: [PATCH 06/82] bump gradle version --- build.gradle | 2 +- gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index f21de848c..e90a68591 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:4.1.3' + classpath 'com.android.tools.build:gradle:4.2.0' } } diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 02d7d70dd..ea1e4b836 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip From ddf597e0d3873633cd848bb87fa6e77ece848e46 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 6 May 2021 18:40:35 +0200 Subject: [PATCH 07/82] invoke x509 verification upon receiving prekey message in rtp session --- .../crypto/axolotl/AxolotlService.java | 144 +++++++++++------- .../xmpp/jingle/JingleRtpConnection.java | 72 ++++++--- .../xmpp/jingle/stanzas/Reason.java | 4 +- 3 files changed, 143 insertions(+), 77 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java index c2d111e4b..81bfb8a46 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -8,7 +8,13 @@ import android.util.Pair; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import com.google.common.base.Function; +import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; +import com.google.common.util.concurrent.SettableFuture; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.whispersystems.libsignal.IdentityKey; @@ -733,58 +739,62 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { axolotlStore.setFingerprintStatus(fingerprint, status); } - private void verifySessionWithPEP(final XmppAxolotlSession session) { + private ListenableFuture verifySessionWithPEP(final XmppAxolotlSession session) { Log.d(Config.LOGTAG, "trying to verify fresh session (" + session.getRemoteAddress().getName() + ") with pep"); final SignalProtocolAddress address = session.getRemoteAddress(); final IdentityKey identityKey = session.getIdentityKey(); + final Jid jid; try { - IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveVerificationForDevice(Jid.of(address.getName()), address.getDeviceId()); - mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - Pair verification = mXmppConnectionService.getIqParser().verification(packet); - if (verification != null) { - try { - Signature verifier = Signature.getInstance("sha256WithRSA"); - verifier.initVerify(verification.first[0]); - verifier.update(identityKey.serialize()); - if (verifier.verify(verification.second)) { - try { - mXmppConnectionService.getMemorizingTrustManager().getNonInteractive().checkClientTrusted(verification.first, "RSA"); - String fingerprint = session.getFingerprint(); - Log.d(Config.LOGTAG, "verified session with x.509 signature. fingerprint was: " + fingerprint); - setFingerprintTrust(fingerprint, FingerprintStatus.createActiveVerified(true)); - axolotlStore.setFingerprintCertificate(fingerprint, verification.first[0]); - fetchStatusMap.put(address, FetchStatus.SUCCESS_VERIFIED); - Bundle information = CryptoHelper.extractCertificateInformation(verification.first[0]); - try { - final String cn = information.getString("subject_cn"); - final Jid jid = Jid.of(address.getName()); - Log.d(Config.LOGTAG, "setting common name for " + jid + " to " + cn); - account.getRoster().getContact(jid).setCommonName(cn); - } catch (final IllegalArgumentException ignored) { - //ignored - } - finishBuildingSessionsFromPEP(address); - return; - } catch (Exception e) { - Log.d(Config.LOGTAG, "could not verify certificate"); - } - } - } catch (Exception e) { - Log.d(Config.LOGTAG, "error during verification " + e.getMessage()); - } - } else { - Log.d(Config.LOGTAG, "no verification found"); - } - fetchStatusMap.put(address, FetchStatus.SUCCESS); - finishBuildingSessionsFromPEP(address); - } - }); - } catch (IllegalArgumentException e) { + jid = Jid.of(address.getName()); + } catch (final IllegalArgumentException e) { fetchStatusMap.put(address, FetchStatus.SUCCESS); finishBuildingSessionsFromPEP(address); + return Futures.immediateFuture(session); } + final SettableFuture future = SettableFuture.create(); + final IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveVerificationForDevice(jid, address.getDeviceId()); + mXmppConnectionService.sendIqPacket(account, packet, (account, response) -> { + Pair verification = mXmppConnectionService.getIqParser().verification(response); + if (verification != null) { + try { + Signature verifier = Signature.getInstance("sha256WithRSA"); + verifier.initVerify(verification.first[0]); + verifier.update(identityKey.serialize()); + if (verifier.verify(verification.second)) { + try { + mXmppConnectionService.getMemorizingTrustManager().getNonInteractive().checkClientTrusted(verification.first, "RSA"); + String fingerprint = session.getFingerprint(); + Log.d(Config.LOGTAG, "verified session with x.509 signature. fingerprint was: " + fingerprint); + setFingerprintTrust(fingerprint, FingerprintStatus.createActiveVerified(true)); + axolotlStore.setFingerprintCertificate(fingerprint, verification.first[0]); + fetchStatusMap.put(address, FetchStatus.SUCCESS_VERIFIED); + Bundle information = CryptoHelper.extractCertificateInformation(verification.first[0]); + try { + final String cn = information.getString("subject_cn"); + final Jid jid1 = Jid.of(address.getName()); + Log.d(Config.LOGTAG, "setting common name for " + jid1 + " to " + cn); + account.getRoster().getContact(jid1).setCommonName(cn); + } catch (final IllegalArgumentException ignored) { + //ignored + } + finishBuildingSessionsFromPEP(address); + future.set(session); + return; + } catch (Exception e) { + Log.d(Config.LOGTAG, "could not verify certificate"); + } + } + } catch (Exception e) { + Log.d(Config.LOGTAG, "error during verification " + e.getMessage()); + } + } else { + Log.d(Config.LOGTAG, "no verification found"); + } + fetchStatusMap.put(address, FetchStatus.SUCCESS); + finishBuildingSessionsFromPEP(address); + future.set(session); + }); + return future; } private void finishBuildingSessionsFromPEP(final SignalProtocolAddress address) { @@ -1255,12 +1265,18 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { ); } - public OmemoVerifiedPayload decrypt(OmemoVerifiedRtpContentMap omemoVerifiedRtpContentMap, final Jid from) throws CryptoFailedException { + public ListenableFuture> decrypt(OmemoVerifiedRtpContentMap omemoVerifiedRtpContentMap, final Jid from) { final ImmutableMap.Builder descriptionTransportBuilder = new ImmutableMap.Builder<>(); final OmemoVerification omemoVerification = new OmemoVerification(); + final ImmutableList.Builder> pepVerificationFutures = new ImmutableList.Builder<>(); for (final Map.Entry content : omemoVerifiedRtpContentMap.contents.entrySet()) { final RtpContentMap.DescriptionTransport descriptionTransport = content.getValue(); - final OmemoVerifiedPayload decryptedTransport = decrypt((OmemoVerifiedIceUdpTransportInfo) descriptionTransport.transport, from); + final OmemoVerifiedPayload decryptedTransport; + try { + decryptedTransport = decrypt((OmemoVerifiedIceUdpTransportInfo) descriptionTransport.transport, from, pepVerificationFutures); + } catch (CryptoFailedException e) { + return Futures.immediateFailedFuture(e); + } omemoVerification.setOrEnsureEqual(decryptedTransport); descriptionTransportBuilder.put( content.getKey(), @@ -1268,13 +1284,26 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { ); } processPostponed(); - return new OmemoVerifiedPayload<>( - omemoVerification, - new RtpContentMap(omemoVerifiedRtpContentMap.group, descriptionTransportBuilder.build()) + final ImmutableList> sessionFutures = pepVerificationFutures.build(); + return Futures.transform( + Futures.allAsList(sessionFutures), + sessions -> { + if (Config.REQUIRE_RTP_VERIFICATION) { + for (XmppAxolotlSession session : sessions) { + requireVerification(session); + } + } + return new OmemoVerifiedPayload<>( + omemoVerification, + new RtpContentMap(omemoVerifiedRtpContentMap.group, descriptionTransportBuilder.build()) + ); + + }, + MoreExecutors.directExecutor() ); } - private OmemoVerifiedPayload decrypt(final OmemoVerifiedIceUdpTransportInfo verifiedIceUdpTransportInfo, final Jid from) throws CryptoFailedException { + private OmemoVerifiedPayload decrypt(final OmemoVerifiedIceUdpTransportInfo verifiedIceUdpTransportInfo, final Jid from, ImmutableList.Builder> pepVerificationFutures) throws CryptoFailedException { final IceUdpTransportInfo transportInfo = new IceUdpTransportInfo(); transportInfo.setAttributes(verifiedIceUdpTransportInfo.getAttributes()); final OmemoVerification omemoVerification = new OmemoVerification(); @@ -1286,14 +1315,16 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { final Element encrypted = child.findChildEnsureSingle(XmppAxolotlMessage.CONTAINERTAG, AxolotlService.PEP_PREFIX); final XmppAxolotlMessage xmppAxolotlMessage = XmppAxolotlMessage.fromElement(encrypted, from.asBareJid()); final XmppAxolotlSession session = getReceivingSession(xmppAxolotlMessage); - if (Config.REQUIRE_RTP_VERIFICATION) { - requireVerification(session); - } final XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintext = xmppAxolotlMessage.decrypt(session, getOwnDeviceId()); final Integer preKeyId = session.getPreKeyIdAndReset(); if (preKeyId != null) { postponedSessions.add(session); } + if (session.isFresh()) { + pepVerificationFutures.add(putFreshSession(session)); + } else if (Config.REQUIRE_RTP_VERIFICATION) { + pepVerificationFutures.add(Futures.immediateFuture(session)); + } fingerprint.setContent(plaintext.getPlaintext()); omemoVerification.setDeviceId(session.getRemoteAddress().getDeviceId()); omemoVerification.setSessionFingerprint(plaintext.getFingerprint()); @@ -1512,15 +1543,16 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { return keyTransportMessage; } - private void putFreshSession(XmppAxolotlSession session) { + private ListenableFuture putFreshSession(XmppAxolotlSession session) { sessions.put(session); if (Config.X509_VERIFICATION) { if (session.getIdentityKey() != null) { - verifySessionWithPEP(session); + return verifySessionWithPEP(session); } else { Log.e(Config.LOGTAG, account.getJid().asBareJid() + ": identity key was empty after reloading for x509 verification"); } } + return Futures.immediateFuture(session); } public enum FetchStatus { 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 3fea3fee6..af1e05497 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -3,6 +3,9 @@ package eu.siacs.conversations.xmpp.jingle; import android.os.SystemClock; import android.util.Log; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.base.Strings; @@ -12,7 +15,10 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Sets; import com.google.common.primitives.Ints; +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; import org.webrtc.EglBase; import org.webrtc.IceCandidate; @@ -243,7 +249,8 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web } private void receiveTransportInfo(final JinglePacket jinglePacket) { - if (isInState(State.SESSION_INITIALIZED, State.SESSION_INITIALIZED_PRE_APPROVED, State.SESSION_ACCEPTED)) { + //Due to the asynchronicity of processing session-init we might move from NULL|PROCEED to INITIALIZED only after transport-info has been received + if (isInState(State.NULL, State.PROCEED, State.SESSION_INITIALIZED, State.SESSION_INITIALIZED_PRE_APPROVED, State.SESSION_ACCEPTED)) { respondOk(jinglePacket); final RtpContentMap contentMap; try { @@ -306,22 +313,19 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web } } - private RtpContentMap receiveRtpContentMap(final JinglePacket jinglePacket, final boolean expectVerification) { + private ListenableFuture receiveRtpContentMap(final JinglePacket jinglePacket, final boolean expectVerification) { final RtpContentMap receivedContentMap = RtpContentMap.of(jinglePacket); if (receivedContentMap instanceof OmemoVerifiedRtpContentMap) { - final AxolotlService.OmemoVerifiedPayload omemoVerifiedPayload; - try { - omemoVerifiedPayload = id.account.getAxolotlService().decrypt((OmemoVerifiedRtpContentMap) receivedContentMap, id.with); - } catch (final CryptoFailedException e) { - throw new SecurityException("Unable to verify DTLS Fingerprint with OMEMO", e); - } - this.omemoVerification.setOrEnsureEqual(omemoVerifiedPayload); - Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": received verifiable DTLS fingerprint via " + this.omemoVerification); - return omemoVerifiedPayload.getPayload(); + final ListenableFuture> future = id.account.getAxolotlService().decrypt((OmemoVerifiedRtpContentMap) receivedContentMap, id.with); + return Futures.transform(future, omemoVerifiedPayload -> { + omemoVerification.setOrEnsureEqual(omemoVerifiedPayload); + Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": received verifiable DTLS fingerprint via " + omemoVerification); + return omemoVerifiedPayload.getPayload(); + }, MoreExecutors.directExecutor()); } else if (expectVerification) { throw new SecurityException("DTLS fingerprint was unexpectedly not verifiable"); } else { - return receivedContentMap; + return Futures.immediateFuture(receivedContentMap); } } @@ -340,9 +344,23 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web } return; } - final RtpContentMap contentMap; + final ListenableFuture future = receiveRtpContentMap(jinglePacket, false); + Futures.addCallback(future, new FutureCallback() { + @Override + public void onSuccess(@Nullable RtpContentMap rtpContentMap) { + receiveSessionInitiate(jinglePacket, rtpContentMap); + } + + @Override + public void onFailure(@NonNull final Throwable throwable) { + respondOk(jinglePacket); + sendSessionTerminate(Reason.ofThrowable(throwable), throwable.getMessage()); + } + }, MoreExecutors.directExecutor()); + } + + private void receiveSessionInitiate(final JinglePacket jinglePacket, final RtpContentMap contentMap) { try { - contentMap = receiveRtpContentMap(jinglePacket, false); contentMap.requireContentDescriptions(); contentMap.requireDTLSFingerprint(); } catch (final RuntimeException e) { @@ -396,9 +414,25 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web terminateWithOutOfOrder(jinglePacket); return; } - final RtpContentMap contentMap; + final ListenableFuture future = receiveRtpContentMap(jinglePacket, this.omemoVerification.hasFingerprint()); + Futures.addCallback(future, new FutureCallback() { + @Override + public void onSuccess(@Nullable RtpContentMap rtpContentMap) { + receiveSessionAccept(jinglePacket, rtpContentMap); + } + + @Override + public void onFailure(@NonNull final Throwable throwable) { + respondOk(jinglePacket); + Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": improperly formatted contents in session-accept", throwable); + webRTCWrapper.close(); + sendSessionTerminate(Reason.ofThrowable(throwable), throwable.getMessage()); + } + }, MoreExecutors.directExecutor()); + } + + private void receiveSessionAccept(final JinglePacket jinglePacket, final RtpContentMap contentMap) { try { - contentMap = receiveRtpContentMap(jinglePacket, this.omemoVerification.hasFingerprint()); contentMap.requireContentDescriptions(); contentMap.requireDTLSFingerprint(); } catch (final RuntimeException e) { @@ -762,7 +796,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web } catch (final WebRTCWrapper.InitializationException e) { Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": unable to initialize WebRTC"); webRTCWrapper.close(); - sendRetract(Reason.ofException(e)); + sendRetract(Reason.ofThrowable(e)); return; } try { @@ -774,7 +808,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web } catch (final Exception e) { Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": unable to sendSessionInitiate", Throwables.getRootCause(e)); webRTCWrapper.close(); - final Reason reason = Reason.ofException(e); + final Reason reason = Reason.ofThrowable(e); if (isInState(targetState)) { sendSessionTerminate(reason); } else { @@ -1010,7 +1044,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web return false; } final FingerprintStatus status = id.account.getAxolotlService().getFingerprintTrust(fingerprint); - return status != null && status.getTrust() == FingerprintStatus.Trust.VERIFIED; + return status != null && status.isVerified(); } public synchronized void acceptCall() { diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/Reason.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/Reason.java index 365fda070..0d6e60fde 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/Reason.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/Reason.java @@ -54,8 +54,8 @@ public enum Reason { } } - public static Reason ofException(final Exception e) { - final Throwable root = Throwables.getRootCause(e); + public static Reason ofThrowable(final Throwable throwable) { + final Throwable root = Throwables.getRootCause(throwable); if (root instanceof RuntimeException) { return of((RuntimeException) root); } From 337aa4a1106e07f6c7fb003b5de7526fa6a79caf Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Fri, 7 May 2021 22:55:20 +0200 Subject: [PATCH 08/82] consider Config.REQUIRE_RTP_VERIFICATION on decrypt. fail as future --- .../conversations/xmpp/jingle/JingleRtpConnection.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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 af1e05497..c933439e7 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -322,8 +322,10 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": received verifiable DTLS fingerprint via " + omemoVerification); return omemoVerifiedPayload.getPayload(); }, MoreExecutors.directExecutor()); - } else if (expectVerification) { - throw new SecurityException("DTLS fingerprint was unexpectedly not verifiable"); + } else if (Config.REQUIRE_RTP_VERIFICATION || expectVerification) { + return Futures.immediateFailedFuture( + new SecurityException("DTLS fingerprint was unexpectedly not verifiable") + ); } else { return Futures.immediateFuture(receivedContentMap); } From 8d391753d74a441099b3b2372f3cacc35cb382d1 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 8 May 2021 08:45:31 +0200 Subject: [PATCH 09/82] encrypt rtp map as future --- .../crypto/axolotl/AxolotlService.java | 45 +++-- .../xmpp/jingle/JingleRtpConnection.java | 160 ++++++++++++------ .../xmpp/jingle/stanzas/Reason.java | 4 + 3 files changed, 145 insertions(+), 64 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java index 81bfb8a46..afd3bf6f3 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -8,7 +8,6 @@ import android.util.Pair; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import com.google.common.base.Function; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.util.concurrent.Futures; @@ -1238,31 +1237,51 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { } - public OmemoVerifiedPayload encrypt(final RtpContentMap rtpContentMap, final Jid jid, final int deviceId) throws CryptoFailedException { - final SignalProtocolAddress address = new SignalProtocolAddress(jid.asBareJid().toString(), deviceId); - final XmppAxolotlSession session = sessions.get(address); - if (session == null) { - throw new CryptoFailedException(String.format("No session found for %d", deviceId)); - } + public ListenableFuture> encrypt(final RtpContentMap rtpContentMap, final Jid jid, final int deviceId) { + return Futures.transformAsync( + getSession(jid, deviceId), + session -> encrypt(rtpContentMap, session), + MoreExecutors.directExecutor() + ); + } + + private ListenableFuture> encrypt(final RtpContentMap rtpContentMap, final XmppAxolotlSession session) { if (Config.REQUIRE_RTP_VERIFICATION) { requireVerification(session); } final ImmutableMap.Builder descriptionTransportBuilder = new ImmutableMap.Builder<>(); final OmemoVerification omemoVerification = new OmemoVerification(); - omemoVerification.setDeviceId(deviceId); + omemoVerification.setDeviceId(session.getRemoteAddress().getDeviceId()); omemoVerification.setSessionFingerprint(session.getFingerprint()); for (final Map.Entry content : rtpContentMap.contents.entrySet()) { final RtpContentMap.DescriptionTransport descriptionTransport = content.getValue(); - final OmemoVerifiedIceUdpTransportInfo encryptedTransportInfo = encrypt(descriptionTransport.transport, session); + final OmemoVerifiedIceUdpTransportInfo encryptedTransportInfo; + try { + encryptedTransportInfo = encrypt(descriptionTransport.transport, session); + } catch (final CryptoFailedException e) { + return Futures.immediateFailedFuture(e); + } descriptionTransportBuilder.put( content.getKey(), new RtpContentMap.DescriptionTransport(descriptionTransport.description, encryptedTransportInfo) ); } - return new OmemoVerifiedPayload<>( - omemoVerification, - new OmemoVerifiedRtpContentMap(rtpContentMap.group, descriptionTransportBuilder.build()) - ); + return Futures.immediateFuture( + new OmemoVerifiedPayload<>( + omemoVerification, + new OmemoVerifiedRtpContentMap(rtpContentMap.group, descriptionTransportBuilder.build()) + )); + } + + private ListenableFuture getSession(final Jid jid, final int deviceId) { + final SignalProtocolAddress address = new SignalProtocolAddress(jid.asBareJid().toString(), deviceId); + final XmppAxolotlSession session = sessions.get(address); + if (session == null) { + return Futures.immediateFailedFuture( + new CryptoFailedException(String.format("No session found for %d", deviceId)) + ); + } + return Futures.immediateFuture(session); } public ListenableFuture> decrypt(OmemoVerifiedRtpContentMap omemoVerifiedRtpContentMap, final Jid from) { 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 c933439e7..0d147d2af 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -318,6 +318,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web if (receivedContentMap instanceof OmemoVerifiedRtpContentMap) { final ListenableFuture> future = id.account.getAxolotlService().decrypt((OmemoVerifiedRtpContentMap) receivedContentMap, id.with); return Futures.transform(future, omemoVerifiedPayload -> { + //TODO test if an exception here triggers a correct abort omemoVerification.setOrEnsureEqual(omemoVerifiedPayload); Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": received verifiable DTLS fingerprint via " + omemoVerification); return omemoVerifiedPayload.getPayload(); @@ -532,17 +533,18 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web this.webRTCWrapper.setRemoteDescription(sdp).get(); addIceCandidatesFromBlackLog(); org.webrtc.SessionDescription webRTCSessionDescription = this.webRTCWrapper.createAnswer().get(); - final SessionDescription sessionDescription = SessionDescription.parse(webRTCSessionDescription.description); - final RtpContentMap respondingRtpContentMap = RtpContentMap.of(sessionDescription); - sendSessionAccept(respondingRtpContentMap); - this.webRTCWrapper.setLocalDescription(webRTCSessionDescription).get(); + prepareSessionAccept(webRTCSessionDescription); } catch (final Exception e) { - Log.d(Config.LOGTAG, "unable to send session accept", Throwables.getRootCause(e)); - webRTCWrapper.close(); - sendSessionTerminate(Reason.FAILED_APPLICATION); + failureToAcceptSession(e); } } + private void failureToAcceptSession(final Throwable throwable) { + Log.d(Config.LOGTAG, "unable to send session accept", Throwables.getRootCause(throwable)); + webRTCWrapper.close(); + sendSessionTerminate(Reason.ofThrowable(throwable)); + } + private void addIceCandidatesFromBlackLog() { while (!this.pendingIceCandidates.isEmpty()) { processCandidates(this.pendingIceCandidates.poll()); @@ -550,24 +552,49 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web } } - private void sendSessionAccept(final RtpContentMap rtpContentMap) { - this.responderRtpContentMap = rtpContentMap; - this.transitionOrThrow(State.SESSION_ACCEPTED); - final RtpContentMap outgoingContentMap; - if (this.omemoVerification.hasDeviceId()) { - final AxolotlService.OmemoVerifiedPayload verifiedPayload; - try { - verifiedPayload = id.account.getAxolotlService().encrypt(rtpContentMap, id.with, omemoVerification.getDeviceId()); - outgoingContentMap = verifiedPayload.getPayload(); - this.omemoVerification.setOrEnsureEqual(verifiedPayload); - } catch (final Exception e) { - throw new SecurityException("Unable to verify DTLS Fingerprint with OMEMO", e); - } - } else { - outgoingContentMap = rtpContentMap; - } - final JinglePacket sessionAccept = outgoingContentMap.toJinglePacket(JinglePacket.Action.SESSION_ACCEPT, id.sessionId); + private void prepareSessionAccept(final org.webrtc.SessionDescription webRTCSessionDescription) { + final SessionDescription sessionDescription = SessionDescription.parse(webRTCSessionDescription.description); + final RtpContentMap respondingRtpContentMap = RtpContentMap.of(sessionDescription); + this.responderRtpContentMap = respondingRtpContentMap; + final ListenableFuture outgoingContentMapFuture = prepareOutgoingContentMap(respondingRtpContentMap); + Futures.addCallback(outgoingContentMapFuture, + new FutureCallback() { + @Override + public void onSuccess(final RtpContentMap outgoingContentMap) { + sendSessionAccept(outgoingContentMap, webRTCSessionDescription); + } + + @Override + public void onFailure(@NonNull Throwable throwable) { + failureToAcceptSession(throwable); + } + }, + MoreExecutors.directExecutor() + ); + } + + private void sendSessionAccept(final RtpContentMap rtpContentMap, final org.webrtc.SessionDescription webRTCSessionDescription) { + transitionOrThrow(State.SESSION_ACCEPTED); + final JinglePacket sessionAccept = rtpContentMap.toJinglePacket(JinglePacket.Action.SESSION_ACCEPT, id.sessionId); send(sessionAccept); + try { + webRTCWrapper.setLocalDescription(webRTCSessionDescription).get(); + } catch (Exception e) { + failureToAcceptSession(e); + } + } + + private ListenableFuture prepareOutgoingContentMap(final RtpContentMap rtpContentMap) { + if (this.omemoVerification.hasDeviceId()) { + ListenableFuture> verifiedPayloadFuture = id.account.getAxolotlService() + .encrypt(rtpContentMap, id.with, omemoVerification.getDeviceId()); + return Futures.transform(verifiedPayloadFuture, verifiedPayload -> { + omemoVerification.setOrEnsureEqual(verifiedPayload); + return verifiedPayload.getPayload(); + }, MoreExecutors.directExecutor()); + } else { + return Futures.immediateFuture(rtpContentMap); + } } synchronized void deliveryMessage(final Jid from, final Element message, final String serverMessageId, final long timestamp) { @@ -803,19 +830,20 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web } try { org.webrtc.SessionDescription webRTCSessionDescription = this.webRTCWrapper.createOffer().get(); - final SessionDescription sessionDescription = SessionDescription.parse(webRTCSessionDescription.description); - final RtpContentMap rtpContentMap = RtpContentMap.of(sessionDescription); - sendSessionInitiate(rtpContentMap, targetState); - this.webRTCWrapper.setLocalDescription(webRTCSessionDescription).get(); + prepareSessionInitiate(webRTCSessionDescription, targetState); } catch (final Exception e) { - Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": unable to sendSessionInitiate", Throwables.getRootCause(e)); - webRTCWrapper.close(); - final Reason reason = Reason.ofThrowable(e); - if (isInState(targetState)) { - sendSessionTerminate(reason); - } else { - sendRetract(reason); - } + failureToInitiateSession(e, targetState); + } + } + + private void failureToInitiateSession(final Throwable throwable, final State targetState) { + Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": unable to sendSessionInitiate", Throwables.getRootCause(throwable)); + webRTCWrapper.close(); + final Reason reason = Reason.ofThrowable(throwable); + if (isInState(targetState)) { + sendSessionTerminate(reason); + } else { + sendRetract(reason); } } @@ -826,27 +854,57 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web this.finish(); } - private void sendSessionInitiate(final RtpContentMap rtpContentMap, final State targetState) { + private void prepareSessionInitiate(final org.webrtc.SessionDescription webRTCSessionDescription, final State targetState) { + final SessionDescription sessionDescription = SessionDescription.parse(webRTCSessionDescription.description); + final RtpContentMap rtpContentMap = RtpContentMap.of(sessionDescription); this.initiatorRtpContentMap = rtpContentMap; - final RtpContentMap outgoingContentMap = encryptSessionInitiate(rtpContentMap); - final JinglePacket sessionInitiate = outgoingContentMap.toJinglePacket(JinglePacket.Action.SESSION_INITIATE, id.sessionId); - this.transitionOrThrow(targetState); - send(sessionInitiate); + final ListenableFuture outgoingContentMapFuture = encryptSessionInitiate(rtpContentMap); + Futures.addCallback(outgoingContentMapFuture, new FutureCallback() { + @Override + public void onSuccess(final RtpContentMap outgoingContentMap) { + sendSessionInitiate(outgoingContentMap, webRTCSessionDescription, targetState); + } + + @Override + public void onFailure(@NonNull final Throwable throwable) { + failureToInitiateSession(throwable, targetState); + } + }, MoreExecutors.directExecutor()); } - private RtpContentMap encryptSessionInitiate(final RtpContentMap rtpContentMap) { + private void sendSessionInitiate(final RtpContentMap rtpContentMap, final org.webrtc.SessionDescription webRTCSessionDescription, final State targetState) { + this.transitionOrThrow(targetState); + final JinglePacket sessionInitiate = rtpContentMap.toJinglePacket(JinglePacket.Action.SESSION_INITIATE, id.sessionId); + send(sessionInitiate); + try { + this.webRTCWrapper.setLocalDescription(webRTCSessionDescription).get(); + } catch (Exception e) { + failureToInitiateSession(e, targetState); + } + } + + private ListenableFuture encryptSessionInitiate(final RtpContentMap rtpContentMap) { if (this.omemoVerification.hasDeviceId()) { - final AxolotlService.OmemoVerifiedPayload verifiedPayload; - try { - verifiedPayload = id.account.getAxolotlService().encrypt(rtpContentMap, id.with, omemoVerification.getDeviceId()); - } catch (final CryptoFailedException e) { - Log.w(Config.LOGTAG, id.account.getJid().asBareJid() + ": unable to use OMEMO DTLS verification on outgoing session initiate. falling back", e); - return rtpContentMap; + final ListenableFuture> verifiedPayloadFuture = id.account.getAxolotlService() + .encrypt(rtpContentMap, id.with, omemoVerification.getDeviceId()); + final ListenableFuture future = Futures.transform(verifiedPayloadFuture, verifiedPayload -> { + omemoVerification.setSessionFingerprint(verifiedPayload.getFingerprint()); + return verifiedPayload.getPayload(); + }, MoreExecutors.directExecutor()); + if (Config.REQUIRE_RTP_VERIFICATION) { + return future; } - this.omemoVerification.setSessionFingerprint(verifiedPayload.getFingerprint()); - return verifiedPayload.getPayload(); + return Futures.catching( + future, + CryptoFailedException.class, + e -> { + Log.w(Config.LOGTAG, id.account.getJid().asBareJid() + ": unable to use OMEMO DTLS verification on outgoing session initiate. falling back", e); + return rtpContentMap; + }, + MoreExecutors.directExecutor() + ); } else { - return rtpContentMap; + return Futures.immediateFuture(rtpContentMap); } } diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/Reason.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/Reason.java index 0d6e60fde..669a01c49 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/Reason.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/stanzas/Reason.java @@ -6,6 +6,7 @@ import com.google.common.base.CaseFormat; import com.google.common.base.Throwables; import eu.siacs.conversations.crypto.axolotl.AxolotlService; +import eu.siacs.conversations.crypto.axolotl.CryptoFailedException; import eu.siacs.conversations.xmpp.jingle.RtpContentMap; public enum Reason { @@ -59,6 +60,9 @@ public enum Reason { if (root instanceof RuntimeException) { return of((RuntimeException) root); } + if (root instanceof CryptoFailedException) { + return SECURITY_ERROR; + } return FAILED_APPLICATION; } } \ No newline at end of file From faa4c87b5f861de5999a6128cfb3c4ec5d343128 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 8 May 2021 09:25:51 +0200 Subject: [PATCH 10/82] build omemo session when encountering unknown on RTP proceed --- .../crypto/axolotl/AxolotlService.java | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java index afd3bf6f3..4da07af9f 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -909,22 +909,23 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { } } - private void buildSessionFromPEP(final SignalProtocolAddress address) { - buildSessionFromPEP(address, null); + private ListenableFuture buildSessionFromPEP(final SignalProtocolAddress address) { + return buildSessionFromPEP(address, null); } - private void buildSessionFromPEP(final SignalProtocolAddress address, OnSessionBuildFromPep callback) { + private ListenableFuture buildSessionFromPEP(final SignalProtocolAddress address, OnSessionBuildFromPep callback) { + final SettableFuture sessionSettableFuture = SettableFuture.create(); Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building new session for " + address.toString()); if (address.equals(getOwnAxolotlAddress())) { throw new AssertionError("We should NEVER build a session with ourselves. What happened here?!"); } - final Jid jid = Jid.of(address.getName()); final boolean oneOfOurs = jid.asBareJid().equals(account.getJid().asBareJid()); IqPacket bundlesPacket = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(jid, address.getDeviceId()); mXmppConnectionService.sendIqPacket(account, bundlesPacket, (account, packet) -> { if (packet.getType() == IqPacket.TYPE.TIMEOUT) { fetchStatusMap.put(address, FetchStatus.TIMEOUT); + sessionSettableFuture.setException(new CryptoFailedException("Unable to build session. Timeout")); } else if (packet.getType() == IqPacket.TYPE.RESULT) { Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received preKey IQ packet, processing..."); final IqParser parser = mXmppConnectionService.getIqParser(); @@ -937,6 +938,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { if (callback != null) { callback.onSessionBuildFailed(); } + sessionSettableFuture.setException(new CryptoFailedException("Unable to build session. IQ Packet Invalid")); return; } Random random = new Random(); @@ -948,6 +950,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { if (callback != null) { callback.onSessionBuildFailed(); } + sessionSettableFuture.setException(new CryptoFailedException("Unable to build session. No suitable PreKey found")); return; } @@ -962,7 +965,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, bundle.getIdentityKey()); sessions.put(address, session); if (Config.X509_VERIFICATION) { - verifySessionWithPEP(session); //TODO; maybe inject callback in here too + sessionSettableFuture.setFuture(verifySessionWithPEP(session)); //TODO; maybe inject callback in here too } else { FingerprintStatus status = getFingerprintTrust(CryptoHelper.bytesToHex(bundle.getIdentityKey().getPublicKey().serialize())); FetchStatus fetchStatus; @@ -978,6 +981,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { if (callback != null) { callback.onSessionBuildSuccessful(); } + sessionSettableFuture.set(session); } } catch (UntrustedIdentityException | InvalidKeyException e) { Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error building session for " + address + ": " @@ -990,6 +994,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { if (callback != null) { callback.onSessionBuildFailed(); } + sessionSettableFuture.setException(new CryptoFailedException(e)); } } else { fetchStatusMap.put(address, FetchStatus.ERROR); @@ -1003,8 +1008,10 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { if (callback != null) { callback.onSessionBuildFailed(); } + sessionSettableFuture.setException(new CryptoFailedException("Unable to build session. IQ Packet Error")); } }); + return sessionSettableFuture; } private void removeFromDeviceAnnouncement(Integer id) { @@ -1277,9 +1284,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { final SignalProtocolAddress address = new SignalProtocolAddress(jid.asBareJid().toString(), deviceId); final XmppAxolotlSession session = sessions.get(address); if (session == null) { - return Futures.immediateFailedFuture( - new CryptoFailedException(String.format("No session found for %d", deviceId)) - ); + return buildSessionFromPEP(address); } return Futures.immediateFuture(session); } From 9182a300c5d0f6f715502c8be693080386bd3235 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 8 May 2021 10:35:07 +0200 Subject: [PATCH 11/82] report fingerprint missmatch as securiy exception --- .../eu/siacs/conversations/xmpp/jingle/OmemoVerification.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/OmemoVerification.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/OmemoVerification.java index 0be0f2cf7..48ff0672c 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/OmemoVerification.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/OmemoVerification.java @@ -55,7 +55,7 @@ public class OmemoVerification { throw new IllegalStateException("No session fingerprint has been previously provided"); } if (!sessionFingerprint.equals(this.sessionFingerprint)) { - throw new IllegalStateException("Session Fingerprints did not match"); + throw new SecurityException("Session Fingerprints did not match"); } if (this.deviceId == null) { throw new IllegalStateException("No Device Id has been previously provided"); From 7476dccc0e7ceaf54e35bc3c4ce4dfaea7666062 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 8 May 2021 11:21:33 +0200 Subject: [PATCH 12/82] do not fetch keys before making call --- .../eu/siacs/conversations/ui/ConversationFragment.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index 37d7024e0..27858484e 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -1355,9 +1355,6 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke return; } if (hasPermissions(REQUEST_START_AUDIO_CALL, Manifest.permission.RECORD_AUDIO)) { - if (Config.REQUIRE_RTP_VERIFICATION && trustKeysIfNeeded(conversation, REQUEST_START_AUDIO_CALL)) { - return; - } triggerRtpSession(RtpSessionActivity.ACTION_MAKE_VOICE_CALL); } } @@ -1368,9 +1365,6 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke return; } if (hasPermissions(REQUEST_START_VIDEO_CALL, Manifest.permission.RECORD_AUDIO, Manifest.permission.CAMERA)) { - if (Config.REQUIRE_RTP_VERIFICATION && trustKeysIfNeeded(conversation, REQUEST_START_VIDEO_CALL)) { - return; - } triggerRtpSession(RtpSessionActivity.ACTION_MAKE_VIDEO_CALL); } } From 89012b0f8bdd3ee9618af5aa96dc82c13175af7e Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 8 May 2021 11:49:24 +0200 Subject: [PATCH 13/82] synchronize startRinging() to not create multiple vibrate futures --- .../eu/siacs/conversations/services/NotificationService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/eu/siacs/conversations/services/NotificationService.java b/src/main/java/eu/siacs/conversations/services/NotificationService.java index 6ccd2b74f..bbde8e904 100644 --- a/src/main/java/eu/siacs/conversations/services/NotificationService.java +++ b/src/main/java/eu/siacs/conversations/services/NotificationService.java @@ -397,7 +397,7 @@ public class NotificationService { notify(DELIVERY_FAILED_NOTIFICATION_ID, summaryNotification); } - public void startRinging(final AbstractJingleConnection.Id id, final Set media) { + public synchronized void startRinging(final AbstractJingleConnection.Id id, final Set media) { showIncomingCallNotification(id, media); final NotificationManager notificationManager = (NotificationManager) mXmppConnectionService.getSystemService(Context.NOTIFICATION_SERVICE); final int currentInterruptionFilter; From 67e5f839f11f0c458287d9e7e679317e0ece3e50 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 8 May 2021 11:50:18 +0200 Subject: [PATCH 14/82] ignore crypto callbacks when rtp session has already been terminated --- .../xmpp/jingle/JingleRtpConnection.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) 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 0d147d2af..67c32d72a 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -540,6 +540,9 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web } private void failureToAcceptSession(final Throwable throwable) { + if (isTerminated()) { + return; + } Log.d(Config.LOGTAG, "unable to send session accept", Throwables.getRootCause(throwable)); webRTCWrapper.close(); sendSessionTerminate(Reason.ofThrowable(throwable)); @@ -574,6 +577,10 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web } private void sendSessionAccept(final RtpContentMap rtpContentMap, final org.webrtc.SessionDescription webRTCSessionDescription) { + if (isTerminated()) { + Log.w(Config.LOGTAG, id.account.getJid().asBareJid() + ": preparing session accept was too slow. already terminated. nothing to do."); + return; + } transitionOrThrow(State.SESSION_ACCEPTED); final JinglePacket sessionAccept = rtpContentMap.toJinglePacket(JinglePacket.Action.SESSION_ACCEPT, id.sessionId); send(sessionAccept); @@ -837,6 +844,9 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web } private void failureToInitiateSession(final Throwable throwable, final State targetState) { + if (isTerminated()) { + return; + } Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": unable to sendSessionInitiate", Throwables.getRootCause(throwable)); webRTCWrapper.close(); final Reason reason = Reason.ofThrowable(throwable); @@ -873,6 +883,10 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web } private void sendSessionInitiate(final RtpContentMap rtpContentMap, final org.webrtc.SessionDescription webRTCSessionDescription, final State targetState) { + if (isTerminated()) { + Log.w(Config.LOGTAG, id.account.getJid().asBareJid() + ": preparing session was too slow. already terminated. nothing to do."); + return; + } this.transitionOrThrow(targetState); final JinglePacket sessionInitiate = rtpContentMap.toJinglePacket(JinglePacket.Action.SESSION_INITIATE, id.sessionId); send(sessionInitiate); From 60c5906fe9e2f60e772da699b598ee0f70f58f31 Mon Sep 17 00:00:00 2001 From: Dheeraj Chintaluri Date: Sun, 9 May 2021 18:21:42 +0530 Subject: [PATCH 15/82] Update libwebrtc version --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 057850a0f..1696e43d3 100644 --- a/README.md +++ b/README.md @@ -388,7 +388,7 @@ you can get access to the the latest beta version by signing up using [this link **Note:** Starting with version 2.8.0 you will need to compile libwebrtc. [Instructions](https://webrtc.github.io/webrtc-org/native-code/android/) can be found on the WebRTC website. Place the resulting libwebrtc.aar in the `libs/` directory. The PlayStore release currently -uses the stable M81 release and renamed the file name to `libwebrtc-m81.aar` put potentially you can +uses the stable M90 release and renamed the file name to `libwebrtc-m90.aar` put potentially you can reference any file name by modifying `build.gradle`. Make sure to have ANDROID_HOME point to your Android SDK. Use the Android SDK Manager to install missing dependencies. From 56535e07ff73a183bd4b8e02edc3700a3e6b4757 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 9 May 2021 08:56:07 +0200 Subject: [PATCH 16/82] =?UTF-8?q?show=20black=20bars=20in=20video=20call?= =?UTF-8?q?=20when=20video=20orientation=20doesn=E2=80=99t=20match=20scree?= =?UTF-8?q?n=20orientation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit fixes #4056 --- .../conversations/ui/RtpSessionActivity.java | 14 +++++++--- src/main/res/layout/activity_rtp_session.xml | 27 ++++++++++++------- 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java index e9e12d7e3..ad016a3b3 100644 --- a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java @@ -35,6 +35,7 @@ import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import org.checkerframework.checker.nullness.compatqual.NullableDecl; +import org.webrtc.RendererCommon; import org.webrtc.SurfaceViewRenderer; import org.webrtc.VideoTrack; @@ -928,7 +929,7 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe if (END_CARD.contains(state) || state == RtpEndUserState.ENDING_CALL) { binding.localVideo.setVisibility(View.GONE); binding.localVideo.release(); - binding.remoteVideo.setVisibility(View.GONE); + binding.remoteVideoWrapper.setVisibility(View.GONE); binding.remoteVideo.release(); binding.pipLocalMicOffIndicator.setVisibility(View.GONE); if (isPictureInPicture()) { @@ -954,7 +955,7 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe } if (isPictureInPicture() && (state == RtpEndUserState.CONNECTING || state == RtpEndUserState.ACCEPTING_CALL)) { binding.localVideo.setVisibility(View.GONE); - binding.remoteVideo.setVisibility(View.GONE); + binding.remoteVideoWrapper.setVisibility(View.GONE); binding.appBarLayout.setVisibility(View.GONE); binding.pipPlaceholder.setVisibility(View.VISIBLE); binding.pipWarning.setVisibility(View.GONE); @@ -976,12 +977,17 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe if (remoteVideoTrack.isPresent()) { ensureSurfaceViewRendererIsSetup(binding.remoteVideo); addSink(remoteVideoTrack.get(), binding.remoteVideo); + binding.remoteVideo.setScalingType( + RendererCommon.ScalingType.SCALE_ASPECT_FILL, + RendererCommon.ScalingType.SCALE_ASPECT_FIT + ); if (state == RtpEndUserState.CONNECTED) { binding.appBarLayout.setVisibility(View.GONE); getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); + binding.remoteVideoWrapper.setVisibility(View.VISIBLE); } else { getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); - binding.remoteVideo.setVisibility(View.GONE); + binding.remoteVideoWrapper.setVisibility(View.GONE); } if (isPictureInPicture() && !requireRtpConnection().isMicrophoneEnabled()) { binding.pipLocalMicOffIndicator.setVisibility(View.VISIBLE); @@ -990,7 +996,7 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe } } else { getWindow().clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); - binding.remoteVideo.setVisibility(View.GONE); + binding.remoteVideoWrapper.setVisibility(View.GONE); binding.pipLocalMicOffIndicator.setVisibility(View.GONE); } } diff --git a/src/main/res/layout/activity_rtp_session.xml b/src/main/res/layout/activity_rtp_session.xml index a14e899c7..26fa4d496 100644 --- a/src/main/res/layout/activity_rtp_session.xml +++ b/src/main/res/layout/activity_rtp_session.xml @@ -86,6 +86,24 @@ + + + + + + - - Date: Thu, 13 May 2021 09:19:16 +0200 Subject: [PATCH 17/82] bump jxmpp-jid version --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index e90a68591..05c204428 100644 --- a/build.gradle +++ b/build.gradle @@ -65,7 +65,7 @@ dependencies { implementation 'com.makeramen:roundedimageview:2.3.0' implementation "com.wefika:flowlayout:0.4.1" implementation 'net.ypresto.androidtranscoder:android-transcoder:0.3.0' - implementation 'org.jxmpp:jxmpp-jid:0.6.4' + implementation 'org.jxmpp:jxmpp-jid:1.0.1' implementation 'org.osmdroid:osmdroid-android:6.1.10' implementation 'org.hsluv:hsluv:0.2' implementation 'org.conscrypt:conscrypt-android:2.2.1' From 2b9862adea0e56f58474d5d9c9562fab46be57a3 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 13 May 2021 10:27:05 +0200 Subject: [PATCH 18/82] update some libraries --- .../services/MaintenanceReceiver.java | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/src/playstore/java/eu/siacs/conversations/services/MaintenanceReceiver.java b/src/playstore/java/eu/siacs/conversations/services/MaintenanceReceiver.java index 24212cf34..2a08cf676 100644 --- a/src/playstore/java/eu/siacs/conversations/services/MaintenanceReceiver.java +++ b/src/playstore/java/eu/siacs/conversations/services/MaintenanceReceiver.java @@ -5,9 +5,7 @@ import android.content.Context; import android.content.Intent; import android.util.Log; -import com.google.firebase.iid.FirebaseInstanceId; - -import java.io.IOException; +import com.google.firebase.installations.FirebaseInstallations; import eu.siacs.conversations.Config; import eu.siacs.conversations.utils.Compatibility; @@ -23,16 +21,10 @@ public class MaintenanceReceiver extends BroadcastReceiver { } private void renewInstanceToken(final Context context) { - new Thread(() -> { - try { - FirebaseInstanceId.getInstance().deleteInstanceId(); - final Intent intent = new Intent(context, XmppConnectionService.class); + FirebaseInstallations.getInstance().delete().addOnSuccessListener(unused -> { + final Intent intent = new Intent(context, XmppConnectionService.class); intent.setAction(XmppConnectionService.ACTION_FCM_TOKEN_REFRESH); Compatibility.startService(context, intent); - } catch (IOException e) { - Log.d(Config.LOGTAG, "unable to renew instance token", e); - } - }).start(); - + }); } } From c9f1bdc55197e97e32ef937abdaf8b8eecbf7056 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Thu, 13 May 2021 11:05:26 +0200 Subject: [PATCH 19/82] fixup: update some libraries --- build.gradle | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/build.gradle b/build.gradle index 05c204428..e1ea9f7b7 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ import com.android.build.OutputFile buildscript { repositories { google() - jcenter() + mavenCentral() } dependencies { classpath 'com.android.tools.build:gradle:4.2.0' @@ -16,8 +16,8 @@ apply plugin: 'com.android.application' repositories { google() - jcenter() mavenCentral() + jcenter() } configurations { @@ -35,7 +35,7 @@ configurations { dependencies { implementation 'androidx.viewpager:viewpager:1.0.0' - playstoreImplementation('com.google.firebase:firebase-messaging:21.1.0') { + playstoreImplementation('com.google.firebase:firebase-messaging:22.0.0') { exclude group: 'com.google.firebase', module: 'firebase-core' exclude group: 'com.google.firebase', module: 'firebase-analytics' exclude group: 'com.google.firebase', module: 'firebase-measurement-connector' @@ -68,7 +68,7 @@ dependencies { implementation 'org.jxmpp:jxmpp-jid:1.0.1' implementation 'org.osmdroid:osmdroid-android:6.1.10' implementation 'org.hsluv:hsluv:0.2' - implementation 'org.conscrypt:conscrypt-android:2.2.1' + implementation 'org.conscrypt:conscrypt-android:2.5.2' implementation 'me.drakeet.support:toastcompat:1.1.0' implementation "com.leinardi.android:speed-dial:2.0.1" @@ -76,7 +76,7 @@ dependencies { implementation "com.squareup.retrofit2:converter-gson:2.9.0" implementation "com.squareup.okhttp3:okhttp:4.9.1" - implementation 'com.google.guava:guava:30.1-android' + implementation 'com.google.guava:guava:30.1.1-android' quicksyImplementation 'io.michaelrocks:libphonenumber-android:8.12.18' implementation fileTree(include: ['libwebrtc-m90.aar'], dir: 'libs') } From e02aaed7d23d176d95bdcc6ed20653ab286c441d Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Fri, 14 May 2021 08:37:42 +0200 Subject: [PATCH 20/82] call SVGs 'vector graphic' instead of 'image' --- .../conversations/ui/adapter/MediaAdapter.java | 2 ++ .../eu/siacs/conversations/utils/UIHelper.java | 7 +++---- .../res/drawable-hdpi/ic_image_black_48dp.png | Bin 0 -> 424 bytes .../res/drawable-hdpi/ic_image_white_48dp.png | Bin 0 -> 450 bytes .../res/drawable-mdpi/ic_image_black_48dp.png | Bin 0 -> 295 bytes .../res/drawable-mdpi/ic_image_white_48dp.png | Bin 0 -> 304 bytes .../res/drawable-xhdpi/ic_image_black_48dp.png | Bin 0 -> 548 bytes .../res/drawable-xhdpi/ic_image_white_48dp.png | Bin 0 -> 570 bytes .../res/drawable-xxhdpi/ic_image_black_48dp.png | Bin 0 -> 807 bytes .../res/drawable-xxhdpi/ic_image_white_48dp.png | Bin 0 -> 859 bytes .../res/drawable-xxxhdpi/ic_image_black_48dp.png | Bin 0 -> 1108 bytes .../res/drawable-xxxhdpi/ic_image_white_48dp.png | Bin 0 -> 1178 bytes src/main/res/values/attrs.xml | 1 + src/main/res/values/strings.xml | 1 + src/main/res/values/themes.xml | 8 ++++---- 15 files changed, 11 insertions(+), 8 deletions(-) create mode 100644 src/main/res/drawable-hdpi/ic_image_black_48dp.png create mode 100644 src/main/res/drawable-hdpi/ic_image_white_48dp.png create mode 100644 src/main/res/drawable-mdpi/ic_image_black_48dp.png create mode 100644 src/main/res/drawable-mdpi/ic_image_white_48dp.png create mode 100644 src/main/res/drawable-xhdpi/ic_image_black_48dp.png create mode 100644 src/main/res/drawable-xhdpi/ic_image_white_48dp.png create mode 100644 src/main/res/drawable-xxhdpi/ic_image_black_48dp.png create mode 100644 src/main/res/drawable-xxhdpi/ic_image_white_48dp.png create mode 100644 src/main/res/drawable-xxxhdpi/ic_image_black_48dp.png create mode 100644 src/main/res/drawable-xxxhdpi/ic_image_white_48dp.png diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/MediaAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/MediaAdapter.java index 75ab964f4..21473dafd 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MediaAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MediaAdapter.java @@ -92,6 +92,8 @@ public class MediaAdapter extends RecyclerView.Adapter0IdihI998&O^-W#$ zE8eR<`gyLR?O%&Chls5choVhVmCp&?Gd)wx?0#|VP~L8%%w$p-R(yr$;{~C~*0aQ- zs?IgBWz4kTp0!A|+(Jmj5e0owZ&G_RIaKonL&qxNl4CJJy$iIb&5VA0&o>}y-{Vf+ zDHG1r1x#19Qr&G{7AbO*Pb!!_%zNG?L)Xr;3&OX}$oL zDwTSQHR(l^wA|V&5Tv(BaLS|4S6@E=HsQVMvgUa=7p*v^YH9g=O3?HaqZf1cUXtJS zaF%GD*rb`FVdQ&MBb@09A9bRsaA1 literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-hdpi/ic_image_white_48dp.png b/src/main/res/drawable-hdpi/ic_image_white_48dp.png new file mode 100644 index 0000000000000000000000000000000000000000..f9f1defa6df89b5a7a68df6787a4ba799d3bd3b2 GIT binary patch literal 450 zcmV;z0X_bSP)q8m|@f&vEk zXhJXy z)&q8Jdy{=h?m>-(r*@dK*d3epCi{|HgU+qz4*BYtIwp6bB0wo2#+nm8I>3B8iEGdk zTNzJQxB%cRn_*v)JJ1{dbN|paSFGhz>`QV3I>zMAKdKnfM6N&`Di|k|EDy3@h4PVK zk{o2e3gsieq-8-3jN^&)U)Wy&nw9O-LW-|?GEfU&#WGOH2p`=L$UkuoJ#eZRDaa7g sq|daVQ|=KHU?d>_qaXz-NI@l=ck_!~>**H-(*OVf07*qoM6N<$f*#evGXMYp literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-mdpi/ic_image_black_48dp.png b/src/main/res/drawable-mdpi/ic_image_black_48dp.png new file mode 100644 index 0000000000000000000000000000000000000000..6b7cd7838b5ab18a63036d58a054a7f7588df8d5 GIT binary patch literal 295 zcmV+?0oeYDP)A-PL#CT5Az5qiARtADF310vJ002ovPDHLkV1k7`cx3

0++mR7nD4pd~x$TQLGba6~{cu-M!3I>jYZG8L#~C$dSzGCa?>% zii{qBH_l~3H}Ow_R&mWIA6&_uY{DqeDkOK@%2_vI7ibl>$R-Q|t-=u5gh8NHSX1ob zZ91p^`0a>6o%RQ$Cqo6Qpk>-nPOXnuLHxL2AA)bf8YiVe5h%F;00009Znszw-prK{(xhG7jYOgXiB))yeH<#dZofRb8aKMh65v4Km(J=0tQxe zrh}*HlLr2tohObjWUF~<#BlwG<&g&AcZFKgNe6i} zfNYr$^8};!yxS34JUMjJ3C3@0i#mVas^%|TnZ!1yV0v))k9T{%%dlH>PGl;JESr7* z+|{QE>WW?mxX)euR&jJ~e$Hi8Zm9Tj<;&2I@{RDB)ul%4IcxY~~shuCjiJz7=%!}+XkP3S}BH?LlOXmQ{1 zbtX&xyQ`v;<^mP(Zkg7~vthON3ayZa*jUD$>Ib8bzq<89<;SOp*oW$l^*Ndhtbf|& z_sOY?7IbX4S=nH_$2U{x*PlA}wm*#j?DqCp9#AQ`XVkQh)Ab4a%EbAC=|-u?1WQ}Q jpDR|sd7z>M1oey&Pa+y;l`K*O#y*3mtDnm{r-UW|Gl}p9 literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-xhdpi/ic_image_white_48dp.png b/src/main/res/drawable-xhdpi/ic_image_white_48dp.png new file mode 100644 index 0000000000000000000000000000000000000000..2ffdb55f264ecd3610f90890f8202f93c00f72e1 GIT binary patch literal 570 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD0wg^q?%&M7z{Ke3;uuoF`1aP>Ea5IfQowI9-hvuwR$tP^fy(>8sSO2|8{wRtGpvc@#B$n~B~Rwm&yIIB)&jy;7gW zTKL?7mzK@v|L<(N|7b3=;GHmT*|XKxR_ zisKEuaTo73N#D@a7GnOhx@e+huI&M-u%#?A#q8EMMfY92V!WoXj#HDtTEZ^x>C~Zf8IWQby>8Ib0dT5wN0yQ*dG`x>m;y#Wc*hW`8x0OY*zD)2SPUjd28<` z%OxHB?A^isVY&9DKSD{2WgAXtCo%nb*|kec?7;kW6+k(LjH+Fa;+@JJK3|^tKESqacg#PH`B$rqARC7 zd%MkZMt5@hvrjR8&%gY(dA<2((|McjgN|CNN-%Kv%%!AW*-s8>?V(X;E-WZ=woz8* zFEJ8Z8N85l+X?3RzQbC^vr&$_DLwCFYoK;RUbat z-(B8t$Twx#pUf9UUF+-p4kt%=?q$)cm$I6Ze!W)eR=$#aqnhS(wd)(6-gA*-GW|JS zwZo2 z%KZE#Z{mW(7oYXad065%|NC>kxCb-epE^Dz$V+o6lvp*}@PG(!*bSAbW|7Mds?gT^6a-xmTi-mqa@VKe_ZakD+%`@Zp7xzarI_hplX7t#Zmc+sY!f zKxw`7Syq&tJaKvN&#$O5+^n z_ZshCtU3QBIX7$xUq~V2?OUGv9^CiWOO(^T%r@&-SBo8x!57l$26w)GSoAitg70-fYL!Fs4D0pxH?OYK z0NN{Zf#=z|*WcgR*Nc5u?c#q@FLg`a++*_Hs>gHM{%H#_+_K}n{jTkM=8OGCMic&L zzS!!1F0(?iSR7~#w@Gj1y7uXB-q=0U`Qt6Zc*2r*@AcEw=PzHVcePRY52Tj=sDEu@ z)5aPl$$#R%$(Q?;v)+Ln$oB+j`0kTi8*JWri^v1@Jzn3z47B=KyrldITi(a#Ydaq? z?_8evXQzIk&w|W1`muuSKm&UA8<_#!An{+divgs%zH<>nZzAWK4)!TkkhocJrtypa z|JrST^y|!=&hWU$4*ufI45QKb?W29#vZ|nZltoPJMC@EnvB;$6kXs3ztjAWe{+Y7z j83S_qnT3>)zHxug>)y95A}|=3X&5|R{an^LB{Ts5Ixu`p literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-xxxhdpi/ic_image_black_48dp.png b/src/main/res/drawable-xxxhdpi/ic_image_black_48dp.png new file mode 100644 index 0000000000000000000000000000000000000000..ab700a31baa62ff83bb872ceae6838e546c32080 GIT binary patch literal 1108 zcmeAS@N?(olHy`uVBq!ia0vp^2SAvE1xWt5x}=AJfkoQW#WAFU@$HSfSu&+E?H`W^ zcm_!yyuxj8b6Xa_&c1t%H&!jzeS6c%RnX-N*CLJ9Y4f@|rC-Oi>Wh3d5SSuyQCPIb za^lvjT1#H#MgMOSkT`KpW%ir=WtVI1&dWZD{x$!;lIJ891Y$m0(8jkqW~u+K!>@~5 zL#^hdwcS1>sP|DklV|riqxKb7O~iLPNizT!sW zpU=- zt+LvQ?t0E5nFs7?CxHwZh3ob<=M-|;S1c3Pm}Kct^FCPNi)ul`otL2sKU5jmACy14 z;dh(ylkR7}Oqai>H@|Ut#xT3~oXYPVK2o0fK|23bImEw5pRKL&zEv!5?AYk2-^hID zk;QDeCsLXIe^2Nf&aghfROw%w?7#fFFpswMdq%q_dY>MMURqZ@an7u`rh<$6uO8UF zB=#%g(MR!3YUtlEa%!h2%mK4spkrq6D6=lo%dSa!zwkKvV< zUu7(}?qj*=R#7#zx^4Cn*>7T&ru%{XDj^LE`Mg~gM4 zk27$;xpKE=+uN64RVu#%RWtdmH`-j|uk}*3P5ifz3%BLoQ#LW5Y^NCRc`;of>cq>? zcNGV}UzqMAthl?F-Qx7SJz@n_u6tQ79`ajX^gh>e@4V#>XZ0Po9Jri4;9(O31Hi(2Q$J&k*w zy6SS?Kaf*9H?i>u6jUAUuI4;+bbj1NrTC_TkITdNoS3c<^XKK&Hw389{_Af5m7r2a zh*T%Nn^9(g|E5D#ExVmI9op3rts0rA-ST?Enhmap=Bh;|s&kqL|Cqccw}Ag+gc|Re zX)BH_Y@EO7TJpTE7=@eD&IQiQ+^6)^_}YvmPP)66ob8F9u{h~?k?@b_^(|6cBGe_b z&je1^JlFqIJg~pmCup^ptl6`L-x-YH`SEG-y3UHx3vIVCg!01&SITmS$7 literal 0 HcmV?d00001 diff --git a/src/main/res/drawable-xxxhdpi/ic_image_white_48dp.png b/src/main/res/drawable-xxxhdpi/ic_image_white_48dp.png new file mode 100644 index 0000000000000000000000000000000000000000..7d5091ded87b138183db10e24afabf288766a598 GIT binary patch literal 1178 zcmeAS@N?(olHy`uVBq!ia0vp^2SAvE1xWt5x}=AJfhEz?#WAFU@$HTH?kQj8+duku zKYX{uF`Hi@cI&L>CHv1STzVC9adTs@QpXDS4X%nB^W3tnZ`ySIQ@Hr>P|%Evb3!hx zzwfywO3LDCPj)!Fr0?51<-3c|*Y@9^`MS!!x%Ob)`|~X`(ZS6HH_mc~MLg!RRog2! zJuA-3&R9n##Jv_m6CNCPra{IDmJ~vG^M`E_1vox7LE1u zZ+cJI-^TFQw6Z<$tMz%I=m_tI{lDhSee=L(@k`$%@6{K@7X05n>-UegN3~~oHXgnD zlB1ye@R7H+Cqlcv+CS^eU88tF=;HR}J0o9wscO3MEb21v2gP#1FYiU$-n%B-akshAlUq%dr2Wgkgg9k121<8!eMgm@X4en0Vrb&HYV#*KO+8*l_I& zqe8N82(m7C9>c%OHkirY4+w=Q4>v!7dks_K+@`Km;SRimKl z>HEZW6GOWteoCEW60z9(seA@6+gH_M7tU;7y#q>ySK_qmszmEKFR^C`IltfQyZriR zmrS7D{6M=+k9@yy+ht|83ipKiD$yJB_Pt;D`HX<-`l&h&|6YbBl-4mm_V|NGW>p-IQ#Xc^->KFOt$Tu>=MdQT+{k@GxwV{dvcGrB z-}DaQ6p(qP{Nen6jzCj~4F_C#^fpYFV0SP$ty$$$9P77fvWB=qJ@=Y|TCel#HosBs z(v>(`R@jpo7P + diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index c9ad3d891..87c44a9fd 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -412,6 +412,7 @@ audio video image + vector graphic PDF document Android App Contact diff --git a/src/main/res/values/themes.xml b/src/main/res/values/themes.xml index 0ba1106fe..9e757a626 100644 --- a/src/main/res/values/themes.xml +++ b/src/main/res/values/themes.xml @@ -80,8 +80,8 @@ 24dp 16dp - @drawable/ic_description_black_48dp - + @drawable/ic_image_black_48dp + @drawable/ic_description_black_48dp @drawable/ic_mic_black_48dp @drawable/ic_headset_black_48dp @drawable/ic_room_black_48dp @@ -236,8 +236,8 @@ 24dp 16dp - @drawable/ic_description_white_48dp - + @drawable/ic_image_white_48dp + @drawable/ic_description_white_48dp @drawable/ic_mic_white_48dp @drawable/ic_headset_white_48dp @drawable/ic_room_white_48dp From 76fb0180d60bc99c49a5420c98ccbf98b7f046aa Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 16 May 2021 15:29:45 +0200 Subject: [PATCH 21/82] bump gradle plugin version --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index e1ea9f7b7..855500be6 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:4.2.0' + classpath 'com.android.tools.build:gradle:4.2.1' } } From 0f3181555af9eeb40bea42d7b36ff77e2345ae01 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 16 May 2021 15:32:53 +0200 Subject: [PATCH 22/82] FileParams indicate unavailable file size as null since 0 is a valid file size we should use null to indicate absence --- .../siacs/conversations/entities/Message.java | 19 ++++++++----------- .../http/HttpDownloadConnection.java | 6 +++--- .../ui/ConversationFragment.java | 2 +- .../ui/adapter/MessageAdapter.java | 2 +- .../conversations/utils/MessageUtils.java | 2 +- 5 files changed, 14 insertions(+), 17 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java index 5b8adb9cf..6c53134aa 100644 --- a/src/main/java/eu/siacs/conversations/entities/Message.java +++ b/src/main/java/eu/siacs/conversations/entities/Message.java @@ -8,6 +8,7 @@ import android.util.Log; import com.google.common.base.Strings; import com.google.common.collect.ImmutableSet; +import com.google.common.primitives.Longs; import org.json.JSONException; @@ -849,10 +850,10 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable fileParams.height = parseInt(parts[3]); case 2: fileParams.url = URL.tryParse(parts[0]); - fileParams.size = parseLong(parts[1]); + fileParams.size = Longs.tryParse(parts[1]); break; case 3: - fileParams.size = parseLong(parts[0]); + fileParams.size = Longs.tryParse(parts[0]); fileParams.width = parseInt(parts[1]); fileParams.height = parseInt(parts[2]); break; @@ -861,14 +862,6 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable return fileParams; } - private static long parseLong(String value) { - try { - return Long.parseLong(value); - } catch (NumberFormatException e) { - return 0; - } - } - private static int parseInt(String value) { try { return Integer.parseInt(value); @@ -905,10 +898,14 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable public static class FileParams { public String url; - public long size = 0; + public Long size = null; public int width = 0; public int height = 0; public int runtime = 0; + + public long getSize() { + return size == null ? 0 : size; + } } public void setFingerprint(String fingerprint) { diff --git a/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java index 09e9121ba..b41962d78 100644 --- a/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java +++ b/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java @@ -83,7 +83,7 @@ public class HttpDownloadConnection implements Transferable { final Message.FileParams fileParams = message.getFileParams(); if (message.hasFileOnRemoteHost()) { mUrl = AesGcmURL.of(fileParams.url); - } else if (message.isOOb() && fileParams.url != null && fileParams.size > 0) { + } else if (message.isOOb() && fileParams.url != null && fileParams.size != null) { mUrl = AesGcmURL.of(fileParams.url); } else { mUrl = AesGcmURL.of(message.getBody().split("\n")[0]); @@ -106,8 +106,8 @@ public class HttpDownloadConnection implements Transferable { this.message.setEncryption(Message.ENCRYPTION_NONE); } //TODO add auth tag size to knownFileSize - final long knownFileSize = message.getFileParams().size; - if (knownFileSize > 0 && interactive) { + final Long knownFileSize = message.getFileParams().size; + if (knownFileSize != null && interactive) { this.file.setExpectedSize(knownFileSize); download(true); } else { diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index 27858484e..7bbd4c8e5 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -1863,7 +1863,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke if (!message.hasFileOnRemoteHost() && xmppConnection != null && conversation.getMode() == Conversational.MODE_SINGLE - && !xmppConnection.getFeatures().httpUpload(message.getFileParams().size)) { + && !xmppConnection.getFeatures().httpUpload(message.getFileParams().getSize())) { activity.selectPresence(conversation, () -> { message.setCounterpart(conversation.getNextCounterpart()); activity.xmppConnectionService.resendFailedMessages(message); diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java index caba4ce64..587e834b3 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -184,7 +184,7 @@ public class MessageAdapter extends ArrayAdapter { && message.getMergedStatus() <= Message.STATUS_RECEIVED; if (message.isFileOrImage() || transferable != null || MessageUtils.unInitiatedButKnownSize(message)) { FileParams params = message.getFileParams(); - filesize = params.size > 0 ? UIHelper.filesizeToString(params.size) : null; + filesize = params.size != null ? UIHelper.filesizeToString(params.size) : null; if (transferable != null && (transferable.getStatus() == Transferable.STATUS_FAILED || transferable.getStatus() == Transferable.STATUS_CANCELLED)) { error = true; } diff --git a/src/main/java/eu/siacs/conversations/utils/MessageUtils.java b/src/main/java/eu/siacs/conversations/utils/MessageUtils.java index 9fc6c5ca8..3cb6338db 100644 --- a/src/main/java/eu/siacs/conversations/utils/MessageUtils.java +++ b/src/main/java/eu/siacs/conversations/utils/MessageUtils.java @@ -115,6 +115,6 @@ public class MessageUtils { } public static boolean unInitiatedButKnownSize(Message message) { - return message.getType() == Message.TYPE_TEXT && message.getTransferable() == null && message.isOOb() && message.getFileParams().size > 0 && message.getFileParams().url != null; + return message.getType() == Message.TYPE_TEXT && message.getTransferable() == null && message.isOOb() && message.getFileParams().size != null && message.getFileParams().url != null; } } From b025265f91efad3e288a00b285f5341a6e21e251 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 16 May 2021 16:17:06 +0200 Subject: [PATCH 23/82] execute status code check on HEAD --- .../http/HttpDownloadConnection.java | 84 ++++++++++--------- 1 file changed, 46 insertions(+), 38 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java index b41962d78..fca5cc705 100644 --- a/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java +++ b/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java @@ -318,6 +318,7 @@ public class HttpDownloadConnection implements Transferable { mostRecentCall = client.newCall(request); try { final Response response = mostRecentCall.execute(); + throwOnInvalidCode(response); final String contentLength = response.header("Content-Length"); final String contentType = response.header("Content-Type"); final AbstractConnectionManager.Extension extension = AbstractConnectionManager.Extension.of(mUrl.encodedPath()); @@ -332,7 +333,11 @@ public class HttpDownloadConnection implements Transferable { if (Strings.isNullOrEmpty(contentLength)) { throw new IOException("no content-length found in HEAD response"); } - return Long.parseLong(contentLength, 10); + final long size = Long.parseLong(contentLength, 10); + if (size < 0) { + throw new IOException("Server reported negative file size"); + } + return size; } catch (IOException e) { Log.d(Config.LOGTAG, "io exception during HEAD " + e.getMessage()); throw e; @@ -395,45 +400,41 @@ public class HttpDownloadConnection implements Transferable { final Request request = requestBuilder.build(); mostRecentCall = client.newCall(request); final Response response = mostRecentCall.execute(); - final int code = response.code(); - if (code >= 200 && code <= 299) { - final String contentRange = response.header("Content-Range"); - final boolean serverResumed = tryResume && contentRange != null && contentRange.startsWith("bytes " + resumeSize + "-"); - final InputStream inputStream = response.body().byteStream(); - final OutputStream outputStream; - long transmitted = 0; - if (tryResume && serverResumed) { - Log.d(Config.LOGTAG, "server resumed"); - transmitted = file.getSize(); - updateProgress(Math.round(((double) transmitted / expected) * 100)); - outputStream = AbstractConnectionManager.createOutputStream(file, true, false); - } else { - final String contentLength = response.header("Content-Length"); - final long size = Strings.isNullOrEmpty(contentLength) ? 0 : Longs.tryParse(contentLength); - if (expected != size) { - Log.d(Config.LOGTAG, "content-length reported on GET (" + size + ") did not match Content-Length reported on HEAD (" + expected + ")"); - } - file.getParentFile().mkdirs(); - if (!file.exists() && !file.createNewFile()) { - throw new FileWriterException(); - } - outputStream = AbstractConnectionManager.createOutputStream(file, false, false); - } - int count; - final byte[] buffer = new byte[4096]; - while ((count = inputStream.read(buffer)) != -1) { - transmitted += count; - try { - outputStream.write(buffer, 0, count); - } catch (IOException e) { - throw new FileWriterException(); - } - updateProgress(Math.round(((double) transmitted / expected) * 100)); - } - outputStream.flush(); + throwOnInvalidCode(response); + final String contentRange = response.header("Content-Range"); + final boolean serverResumed = tryResume && contentRange != null && contentRange.startsWith("bytes " + resumeSize + "-"); + final InputStream inputStream = response.body().byteStream(); + final OutputStream outputStream; + long transmitted = 0; + if (tryResume && serverResumed) { + Log.d(Config.LOGTAG, "server resumed"); + transmitted = file.getSize(); + updateProgress(Math.round(((double) transmitted / expected) * 100)); + outputStream = AbstractConnectionManager.createOutputStream(file, true, false); } else { - throw new IOException(String.format(Locale.ENGLISH, "HTTP Status code was %d", code)); + final String contentLength = response.header("Content-Length"); + final long size = Strings.isNullOrEmpty(contentLength) ? 0 : Longs.tryParse(contentLength); + if (expected != size) { + Log.d(Config.LOGTAG, "content-length reported on GET (" + size + ") did not match Content-Length reported on HEAD (" + expected + ")"); + } + file.getParentFile().mkdirs(); + if (!file.exists() && !file.createNewFile()) { + throw new FileWriterException(); + } + outputStream = AbstractConnectionManager.createOutputStream(file, false, false); } + int count; + final byte[] buffer = new byte[4096]; + while ((count = inputStream.read(buffer)) != -1) { + transmitted += count; + try { + outputStream.write(buffer, 0, count); + } catch (IOException e) { + throw new FileWriterException(); + } + updateProgress(Math.round(((double) transmitted / expected) * 100)); + } + outputStream.flush(); } private void updateImageBounds() { @@ -451,4 +452,11 @@ public class HttpDownloadConnection implements Transferable { } } + + private static void throwOnInvalidCode(final Response response) throws IOException { + final int code = response.code(); + if (code < 200 || code >= 300) { + throw new IOException(String.format(Locale.ENGLISH, "HTTP Status code was %d", code)); + } + } } From 87f99d35708d09a52c1c99b2d114e4c1c07007ef Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Mon, 17 May 2021 15:51:21 +0200 Subject: [PATCH 24/82] Transferables interface needs to differentiate between 0 and null file size --- .../eu/siacs/conversations/entities/Transferable.java | 2 +- .../entities/TransferablePlaceholder.java | 4 ++-- .../conversations/http/HttpDownloadConnection.java | 6 ++++-- .../conversations/http/HttpUploadConnection.java | 4 ++-- .../services/AbstractConnectionManager.java | 11 +++++------ .../xmpp/jingle/JingleFileTransferConnection.java | 4 ++-- 6 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/entities/Transferable.java b/src/main/java/eu/siacs/conversations/entities/Transferable.java index 6a31a368a..5c833f603 100644 --- a/src/main/java/eu/siacs/conversations/entities/Transferable.java +++ b/src/main/java/eu/siacs/conversations/entities/Transferable.java @@ -22,7 +22,7 @@ public interface Transferable { int getStatus(); - long getFileSize(); + Long getFileSize(); int getProgress(); diff --git a/src/main/java/eu/siacs/conversations/entities/TransferablePlaceholder.java b/src/main/java/eu/siacs/conversations/entities/TransferablePlaceholder.java index c8a60d7d9..fd72a28a0 100644 --- a/src/main/java/eu/siacs/conversations/entities/TransferablePlaceholder.java +++ b/src/main/java/eu/siacs/conversations/entities/TransferablePlaceholder.java @@ -18,8 +18,8 @@ public class TransferablePlaceholder implements Transferable { } @Override - public long getFileSize() { - return 0; + public Long getFileSize() { + return null; } @Override diff --git a/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java index fca5cc705..349886115 100644 --- a/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java +++ b/src/main/java/eu/siacs/conversations/http/HttpDownloadConnection.java @@ -107,6 +107,7 @@ public class HttpDownloadConnection implements Transferable { } //TODO add auth tag size to knownFileSize final Long knownFileSize = message.getFileParams().size; + Log.d(Config.LOGTAG,"knownFileSize: "+knownFileSize+", body="+message.getBody()); if (knownFileSize != null && interactive) { this.file.setExpectedSize(knownFileSize); download(true); @@ -130,6 +131,7 @@ public class HttpDownloadConnection implements Transferable { } private void download(final boolean interactive) { + Log.d(Config.LOGTAG,"download()",new Exception()); EXECUTOR.execute(new FileDownloader(interactive)); } @@ -234,11 +236,11 @@ public class HttpDownloadConnection implements Transferable { } @Override - public long getFileSize() { + public Long getFileSize() { if (this.file != null) { return this.file.getExpectedSize(); } else { - return 0; + return null; } } diff --git a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java index 6611aab2b..f1bae9956 100644 --- a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java +++ b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java @@ -69,8 +69,8 @@ public class HttpUploadConnection implements Transferable, AbstractConnectionMan } @Override - public long getFileSize() { - return file == null ? 0 : file.getExpectedSize(); + public Long getFileSize() { + return file == null ? null : file.getExpectedSize(); } @Override diff --git a/src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java b/src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java index 40e381816..94c6b79b9 100644 --- a/src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java +++ b/src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java @@ -5,6 +5,8 @@ import android.os.PowerManager; import android.os.SystemClock; import android.util.Log; +import androidx.core.content.ContextCompat; + import org.bouncycastle.crypto.engines.AESEngine; import org.bouncycastle.crypto.io.CipherInputStream; import org.bouncycastle.crypto.io.CipherOutputStream; @@ -118,7 +120,8 @@ public class AbstractConnectionManager { } public long getAutoAcceptFileSize() { - return this.mXmppConnectionService.getLongPreference("auto_accept_file_size", R.integer.auto_accept_filesize); + final long autoAcceptFileSize = this.mXmppConnectionService.getLongPreference("auto_accept_file_size", R.integer.auto_accept_filesize); + return autoAcceptFileSize <= 0 ? -1 : autoAcceptFileSize; } public boolean hasStoragePermission() { @@ -134,12 +137,8 @@ public class AbstractConnectionManager { } } - public PowerManager.WakeLock createWakeLock(final Thread thread) { - return createWakeLock("conversations:" + thread.getName()); - } - public PowerManager.WakeLock createWakeLock(final String name) { - final PowerManager powerManager = (PowerManager) mXmppConnectionService.getSystemService(Context.POWER_SERVICE); + final PowerManager powerManager = ContextCompat.getSystemService(mXmppConnectionService, PowerManager.class); return powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, name); } 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 8997a5043..40edf0b2c 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleFileTransferConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleFileTransferConnection.java @@ -1239,11 +1239,11 @@ public class JingleFileTransferConnection extends AbstractJingleConnection imple } @Override - public long getFileSize() { + public Long getFileSize() { if (this.file != null) { return this.file.getExpectedSize(); } else { - return 0; + return null; } } From af33a57bf2c47c3b6a6b7840a5df77262e088f4c Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Mon, 17 May 2021 16:00:00 +0200 Subject: [PATCH 25/82] add description for text/plain --- src/main/java/eu/siacs/conversations/utils/UIHelper.java | 2 ++ src/main/res/values/strings.xml | 1 + 2 files changed, 3 insertions(+) diff --git a/src/main/java/eu/siacs/conversations/utils/UIHelper.java b/src/main/java/eu/siacs/conversations/utils/UIHelper.java index c8b9823ee..6dcf517a0 100644 --- a/src/main/java/eu/siacs/conversations/utils/UIHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/UIHelper.java @@ -502,6 +502,8 @@ public class UIHelper { return context.getString(R.string.ebook); } else if (mime.equals("application/gpx+xml")) { return context.getString(R.string.gpx_track); + } else if (mime.equals("text/plain")) { + return context.getString(R.string.plain_text_document); } else { return mime; } diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 87c44a9fd..9f3ba84ac 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -965,4 +965,5 @@ No active accounts support this feature The backup has been started. You’ll get a notification once it has been completed. Unable to enable video. + Plain text document From 98ffadd87d8a6118af39fb00b2cb02d075e37951 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 18 May 2021 10:11:35 +0200 Subject: [PATCH 26/82] log exception when file is not a ceb --- .../ui/ImportBackupActivity.java | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/conversations/java/eu/siacs/conversations/ui/ImportBackupActivity.java b/src/conversations/java/eu/siacs/conversations/ui/ImportBackupActivity.java index 888c75129..a1f4d8d11 100644 --- a/src/conversations/java/eu/siacs/conversations/ui/ImportBackupActivity.java +++ b/src/conversations/java/eu/siacs/conversations/ui/ImportBackupActivity.java @@ -6,7 +6,6 @@ import android.content.DialogInterface; import android.content.Intent; import android.content.ServiceConnection; import android.net.Uri; -import android.os.Build; import android.os.Bundle; import android.os.IBinder; import android.util.Log; @@ -125,7 +124,8 @@ public class ImportBackupActivity extends ActionBarActivity implements ServiceCo try { final ImportBackupService.BackupFile backupFile = ImportBackupService.BackupFile.read(this, uri); showEnterPasswordDialog(backupFile, finishOnCancel); - } catch (IOException | IllegalArgumentException e) { + } catch (final IOException | IllegalArgumentException e) { + Log.d(Config.LOGTAG, "unable to open backup file " + uri, e); Snackbar.make(binding.coordinator, R.string.not_a_backup_file, Snackbar.LENGTH_LONG).show(); } } @@ -181,6 +181,7 @@ public class ImportBackupActivity extends ActionBarActivity implements ServiceCo @Override public void onActivityResult(int requestCode, int resultCode, Intent intent) { + super.onActivityResult(requestCode, resultCode, intent); if (resultCode == RESULT_OK) { if (requestCode == 0xbac) { openBackupFileFromUri(intent.getData(), false); @@ -225,15 +226,17 @@ public class ImportBackupActivity extends ActionBarActivity implements ServiceCo @Override public boolean onOptionsItemSelected(MenuItem item) { if (item.getItemId() == R.id.action_open_backup_file) { - Intent intent = new Intent(Intent.ACTION_GET_CONTENT); - intent.setType("*/*"); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { - intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, false); - } - intent.addCategory(Intent.CATEGORY_OPENABLE); - startActivityForResult(Intent.createChooser(intent, getString(R.string.open_backup)), 0xbac); + openBackupFile(); return true; } return super.onOptionsItemSelected(item); } + + private void openBackupFile() { + final Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.setType("*/*"); + intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, false); + intent.addCategory(Intent.CATEGORY_OPENABLE); + startActivityForResult(Intent.createChooser(intent, getString(R.string.open_backup)), 0xbac); + } } From 7466d125056307a6c2a767a0e4a95b77343aa1f2 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 22 May 2021 19:37:20 +0200 Subject: [PATCH 27/82] ring during device discovery --- .../java/eu/siacs/conversations/xmpp/jingle/ToneManager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/eu/siacs/conversations/xmpp/jingle/ToneManager.java b/src/main/java/eu/siacs/conversations/xmpp/jingle/ToneManager.java index 60ac67f14..4fb9dee16 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/ToneManager.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/ToneManager.java @@ -37,7 +37,7 @@ class ToneManager { private static ToneState of(final boolean isInitiator, final RtpEndUserState state, final Set media) { if (isInitiator) { - if (asList(RtpEndUserState.RINGING, RtpEndUserState.CONNECTING).contains(state)) { + if (asList(RtpEndUserState.FINDING_DEVICE, RtpEndUserState.RINGING, RtpEndUserState.CONNECTING).contains(state)) { return ToneState.RINGING; } if (state == RtpEndUserState.DECLINED_OR_BUSY) { From b99f9d4f1c597ccdea16ef91b3dc0a9fef390592 Mon Sep 17 00:00:00 2001 From: Alexei Sorokin Date: Sun, 23 May 2021 17:24:07 +0300 Subject: [PATCH 28/82] make search case-insensitive not only for ASCII --- .../conversations/persistance/DatabaseBackend.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index cf6f05ae7..ed6b80c5b 100644 --- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -164,16 +164,16 @@ public class DatabaseBackend extends SQLiteOpenHelper { + "UNIQUE(" + Resolver.Result.DOMAIN + ") ON CONFLICT REPLACE" + ");"; - private static final String CREATE_MESSAGE_TIME_INDEX = "create INDEX message_time_index ON " + Message.TABLENAME + "(" + Message.TIME_SENT + ")"; - private static final String CREATE_MESSAGE_CONVERSATION_INDEX = "create INDEX message_conversation_index ON " + Message.TABLENAME + "(" + Message.CONVERSATION + ")"; - private static final String CREATE_MESSAGE_DELETED_INDEX = "create index message_deleted_index ON " + Message.TABLENAME + "(" + Message.DELETED + ")"; - private static final String CREATE_MESSAGE_RELATIVE_FILE_PATH_INDEX = "create INDEX message_file_path_index ON " + Message.TABLENAME + "(" + Message.RELATIVE_FILE_PATH + ")"; - private static final String CREATE_MESSAGE_TYPE_INDEX = "create INDEX message_type_index ON " + Message.TABLENAME + "(" + Message.TYPE + ")"; + private static final String CREATE_MESSAGE_TIME_INDEX = "CREATE INDEX message_time_index ON " + Message.TABLENAME + "(" + Message.TIME_SENT + ")"; + private static final String CREATE_MESSAGE_CONVERSATION_INDEX = "CREATE INDEX message_conversation_index ON " + Message.TABLENAME + "(" + Message.CONVERSATION + ")"; + private static final String CREATE_MESSAGE_DELETED_INDEX = "CREATE INDEX message_deleted_index ON " + Message.TABLENAME + "(" + Message.DELETED + ")"; + private static final String CREATE_MESSAGE_RELATIVE_FILE_PATH_INDEX = "CREATE INDEX message_file_path_index ON " + Message.TABLENAME + "(" + Message.RELATIVE_FILE_PATH + ")"; + private static final String CREATE_MESSAGE_TYPE_INDEX = "CREATE INDEX message_type_index ON " + Message.TABLENAME + "(" + Message.TYPE + ")"; - private static final String CREATE_MESSAGE_INDEX_TABLE = "CREATE VIRTUAL TABLE messages_index USING FTS4(uuid TEXT PRIMARY KEY, body TEXT)"; + private static final String CREATE_MESSAGE_INDEX_TABLE = "CREATE VIRTUAL TABLE messages_index USING fts4 (uuid TEXT PRIMARY KEY, body TEXT, tokenize = 'unicode61')"; private static final String CREATE_MESSAGE_INSERT_TRIGGER = "CREATE TRIGGER after_message_insert AFTER INSERT ON " + Message.TABLENAME + " BEGIN INSERT INTO messages_index (uuid,body) VALUES (new.uuid,new.body); END;"; private static final String CREATE_MESSAGE_UPDATE_TRIGGER = "CREATE TRIGGER after_message_update UPDATE of uuid,body ON " + Message.TABLENAME + " BEGIN update messages_index set body=new.body,uuid=new.uuid WHERE uuid=old.uuid; END;"; - private static final String COPY_PREEXISTING_ENTRIES = "INSERT into messages_index(uuid,body) select uuid,body FROM " + Message.TABLENAME + ";"; + private static final String COPY_PREEXISTING_ENTRIES = "INSERT INTO messages_index(uuid,body) SELECT uuid,body FROM " + Message.TABLENAME + ";"; private DatabaseBackend(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); From 0e54cde4bfcfeffe8a6eaaef576b6aace8474c46 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Fri, 23 Jul 2021 08:04:36 +0200 Subject: [PATCH 29/82] add omemo media sharing to doap file --- conversations.doap | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/conversations.doap b/conversations.doap index 462d750da..93a7df126 100644 --- a/conversations.doap +++ b/conversations.doap @@ -453,12 +453,19 @@ 0.2.0 + + + + complete + 0.1.0 + + - 2.5.8 - 2019-09-12 - + 2.9.13 + 2021-05-03 + From 1e1dad780b92254d691cf12a96b283e8e1d87f79 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 28 Jul 2021 16:57:57 +0200 Subject: [PATCH 30/82] add .opus file extension to mime table --- src/main/java/eu/siacs/conversations/utils/MimeUtils.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/eu/siacs/conversations/utils/MimeUtils.java b/src/main/java/eu/siacs/conversations/utils/MimeUtils.java index 53e2e1917..b320c0c11 100644 --- a/src/main/java/eu/siacs/conversations/utils/MimeUtils.java +++ b/src/main/java/eu/siacs/conversations/utils/MimeUtils.java @@ -247,6 +247,7 @@ public final class MimeUtils { add("audio/mpeg", "m4a"); add("audio/mpegurl", "m3u"); add("audio/ogg", "oga"); + add("audio/opus", "opus"); add("audio/prs.sid", "sid"); add("audio/x-aiff", "aif"); add("audio/x-aiff", "aiff"); From e528b9f5df59f7b49ae18c73396bd56525493e28 Mon Sep 17 00:00:00 2001 From: Licaon_Kter Date: Wed, 11 Aug 2021 15:42:30 +0000 Subject: [PATCH 31/82] Always show Quote as last action --- src/main/res/menu/message_context.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/res/menu/message_context.xml b/src/main/res/menu/message_context.xml index f32505203..f6592c5af 100644 --- a/src/main/res/menu/message_context.xml +++ b/src/main/res/menu/message_context.xml @@ -20,10 +20,6 @@ android:id="@+id/copy_link" android:title="@string/copy_link" android:visible="false" /> - + - \ No newline at end of file + From 309082a9b39e4dae7570d5f486c9429da0b6d9e5 Mon Sep 17 00:00:00 2001 From: Millesimus Date: Wed, 11 Aug 2021 20:04:19 +0200 Subject: [PATCH 32/82] Fixing xmpp:uri bug in channel details. #4139 --- .../eu/siacs/conversations/ui/ConferenceDetailsActivity.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java index 3d29bb89a..574035e2c 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java @@ -7,6 +7,7 @@ import android.os.Bundle; import android.text.Editable; import android.text.SpannableStringBuilder; import android.text.TextWatcher; +import android.text.method.LinkMovementMethod; import android.view.Menu; import android.view.MenuItem; import android.view.View; @@ -481,6 +482,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers this.binding.mucSubject.setTextAppearance(this, subject.length() > (hasTitle ? 128 : 196) ? R.style.TextAppearance_Conversations_Body1_Linkified : R.style.TextAppearance_Conversations_Subhead); this.binding.mucSubject.setAutoLinkMask(0); this.binding.mucSubject.setVisibility(View.VISIBLE); + this.binding.mucSubject.setMovementMethod(LinkMovementMethod.getInstance()); } else { this.binding.mucSubject.setVisibility(View.GONE); } From 65a72827bc2187202a371a4ef9ce665e26910c6f Mon Sep 17 00:00:00 2001 From: Millesimus Date: Fri, 20 Aug 2021 21:51:51 +0200 Subject: [PATCH 33/82] New helper to help with quotes. --- .../conversations/ui/util/QuoteHelper.java | 51 +++++++++++++++++++ .../siacs/conversations/utils/UIHelper.java | 28 ++++++++++ 2 files changed, 79 insertions(+) create mode 100644 src/main/java/eu/siacs/conversations/ui/util/QuoteHelper.java diff --git a/src/main/java/eu/siacs/conversations/ui/util/QuoteHelper.java b/src/main/java/eu/siacs/conversations/ui/util/QuoteHelper.java new file mode 100644 index 000000000..cfd2a2e63 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/ui/util/QuoteHelper.java @@ -0,0 +1,51 @@ +package eu.siacs.conversations.ui.util; + +import eu.siacs.conversations.utils.UIHelper; + +public class QuoteHelper { + + public static boolean isPositionQuoteCharacter(CharSequence body, int pos){ + return body.charAt(pos) == '>'; + } + + public static boolean isPositionFollowedByQuoteChar(CharSequence body, int pos) { + return body.length() > pos + 1 && isPositionQuoteCharacter(body, pos +1 ); + } + + // 'Prequote' means anything we require or can accept in front of a QuoteChar + public static boolean isPositionPrecededByPrequote(CharSequence body, int pos){ + return UIHelper.isPositionPrecededByLineStart(body, pos); + } + + public static boolean isPositionQuoteStart (CharSequence body, int pos){ + return isPositionQuoteCharacter(body, pos) + && isPositionPrecededByPrequote(body, pos) + && (UIHelper.isPositionFollowedByWhitespace(body, pos) + || isPositionFollowedByQuoteChar(body, pos)); + } + + public static boolean bodyContainsQuoteStart (CharSequence body){ + for (int i = 0; i < body.length(); i++){ + if (isPositionQuoteStart(body, i)){ + return true; + } + } + return false; + } + /*public static int getQuoteColors(XmppActivity activity, boolean darkBackground, int quoteDepth){ + int[] colorsLight = R.style.ConversationsTheme_Dark; + int[] colorsDark = Config.QUOTE_COLOR_ARRAY_DARK; + + Collections.rotate(Collections.singletonList(colorsLight), quoteDepth); + Collections.rotate(Collections.singletonList(colorsDark), quoteDepth); + + Arrays.stream(colorsLight).toArray(); + + int quoteColors = darkBackground ? ContextCompat.getColor(activity, colorsLight[quoteDepth-1]) + : ContextCompat.getColor(activity, colorsDark[quoteDepth-1]); + + Collections.rotate + + return quoteColors; + };*/ +} diff --git a/src/main/java/eu/siacs/conversations/utils/UIHelper.java b/src/main/java/eu/siacs/conversations/utils/UIHelper.java index 6dcf517a0..26e6af2fa 100644 --- a/src/main/java/eu/siacs/conversations/utils/UIHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/UIHelper.java @@ -372,6 +372,34 @@ public class UIHelper { return input.length() > 256 ? StylingHelper.subSequence(input, 0, 256) : input; } + public static boolean isPositionFollowedByWhitespace(CharSequence body, int pos){ + return Character.isWhitespace(body.charAt(pos + 1)); + } + + public static boolean isPositionPrecededByWhitespace(CharSequence body, int pos){ + return Character.isWhitespace(body.charAt(pos -1 )); + } + + public static boolean isPositionPrecededByBodyStart(CharSequence body, int pos){ + // true if not a single linebreak before current position + for (int i = pos - 1; i >= 0; i--){ + if (body.charAt(i) != ' '){ + return false; + } + } + return true; + } + + public static boolean isPositionPrecededByLineStart(CharSequence body, int pos){ + if (isPositionPrecededByBodyStart(body, pos)){ + return true; + } + if (body.charAt(pos - 1) == '\n'){ + return true; + } + return false; + } + public static boolean isPositionFollowedByQuoteableCharacter(CharSequence body, int pos) { return !isPositionFollowedByNumber(body, pos) && !isPositionFollowedByEmoticon(body, pos) From 74d60d01312161c4e875b12decfbf3bd00d51113 Mon Sep 17 00:00:00 2001 From: Millesimus Date: Fri, 20 Aug 2021 21:53:01 +0200 Subject: [PATCH 34/82] Implement nested quotes through iteration. --- .../java/eu/siacs/conversations/Config.java | 3 + .../ui/adapter/MessageAdapter.java | 86 ++++++++++--------- 2 files changed, 50 insertions(+), 39 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/Config.java b/src/main/java/eu/siacs/conversations/Config.java index 5286abf49..3159d931f 100644 --- a/src/main/java/eu/siacs/conversations/Config.java +++ b/src/main/java/eu/siacs/conversations/Config.java @@ -201,4 +201,7 @@ public final class Config { public final static float LOCATION_FIX_SPACE_DELTA = 10; // m public final static int LOCATION_FIX_SIGNIFICANT_TIME_DELTA = 1000 * 60 * 2; // ms } + + // How deep nested quotes should become. '2' means one quote nested in another. + public static final int QUOTE_MAX_DEPTH = 3; } diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java index 587e834b3..5c9104343 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -59,6 +59,7 @@ import eu.siacs.conversations.ui.text.DividerSpan; import eu.siacs.conversations.ui.text.QuoteSpan; import eu.siacs.conversations.ui.util.AvatarWorkerTask; import eu.siacs.conversations.ui.util.MyLinkify; +import eu.siacs.conversations.ui.util.QuoteHelper; import eu.siacs.conversations.ui.util.ViewUtil; import eu.siacs.conversations.ui.widget.ClickableMovementMethod; import eu.siacs.conversations.utils.CryptoHelper; @@ -359,48 +360,55 @@ public class MessageAdapter extends ArrayAdapter { */ private boolean handleTextQuotes(SpannableStringBuilder body, boolean darkBackground) { boolean startsWithQuote = false; - char previous = '\n'; - int lineStart = -1; - int lineTextStart = -1; - int quoteStart = -1; - for (int i = 0; i <= body.length(); i++) { - char current = body.length() > i ? body.charAt(i) : '\n'; - if (lineStart == -1) { - if (previous == '\n') { - if ((current == '>' && UIHelper.isPositionFollowedByQuoteableCharacter(body, i)) - || current == '\u00bb' && !UIHelper.isPositionFollowedByQuote(body, i)) { - // Line start with quote - lineStart = i; - if (quoteStart == -1) quoteStart = i; - if (i == 0) startsWithQuote = true; - } else if (quoteStart >= 0) { - // Line start without quote, apply spans there - applyQuoteSpan(body, quoteStart, i - 1, darkBackground); - quoteStart = -1; + int quoteDepth = 1; + while (QuoteHelper.bodyContainsQuoteStart(body) && quoteDepth <= Config.QUOTE_MAX_DEPTH) { + char previous = '\n'; + int lineStart = -1; + int lineTextStart = -1; + int quoteStart = -1; + for (int i = 0; i <= body.length(); i++) { + char current = body.length() > i ? body.charAt(i) : '\n'; + if (lineStart == -1) { + if (previous == '\n') { + if ( + (QuoteHelper.isPositionQuoteStart(body, i) + || (current == '\u00bb' && !UIHelper.isPositionFollowedByQuote(body, i) + ))) { + // Line start with quote + lineStart = i; + if (quoteStart == -1) quoteStart = i; + if (i == 0) startsWithQuote = true; + } else if (quoteStart >= 0) { + // Line start without quote, apply spans there + applyQuoteSpan(body, quoteStart, i - 1, darkBackground); + quoteStart = -1; + quoteDepth++; + } + } + } else { + // Remove extra spaces between > and first character in the line + // > character will be removed too + if (current != ' ' && lineTextStart == -1) { + lineTextStart = i; + } + if (current == '\n') { + body.delete(lineStart, lineTextStart); + i -= lineTextStart - lineStart; + if (i == lineStart) { + // Avoid empty lines because span over empty line can be hidden + body.insert(i++, " "); + } + lineStart = -1; + lineTextStart = -1; } } - } else { - // Remove extra spaces between > and first character in the line - // > character will be removed too - if (current != ' ' && lineTextStart == -1) { - lineTextStart = i; - } - if (current == '\n') { - body.delete(lineStart, lineTextStart); - i -= lineTextStart - lineStart; - if (i == lineStart) { - // Avoid empty lines because span over empty line can be hidden - body.insert(i++, " "); - } - lineStart = -1; - lineTextStart = -1; - } + previous = current; + } + if (quoteStart >= 0) { + // Apply spans to finishing open quote + applyQuoteSpan(body, quoteStart, body.length(), darkBackground); + quoteDepth++; } - previous = current; - } - if (quoteStart >= 0) { - // Apply spans to finishing open quote - applyQuoteSpan(body, quoteStart, body.length(), darkBackground); } return startsWithQuote; } From e850900b40e5bafc6ece4303bb6f712b87132758 Mon Sep 17 00:00:00 2001 From: Millesimus Date: Fri, 20 Aug 2021 22:40:06 +0200 Subject: [PATCH 35/82] Quoting quotes, limited by nesting depth. --- .../java/eu/siacs/conversations/Config.java | 4 ++- .../conversations/ui/util/QuoteHelper.java | 30 +++++++++---------- .../conversations/ui/widget/EditMessage.java | 2 +- .../conversations/utils/MessageUtils.java | 3 +- 4 files changed, 21 insertions(+), 18 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/Config.java b/src/main/java/eu/siacs/conversations/Config.java index 3159d931f..a8494d68c 100644 --- a/src/main/java/eu/siacs/conversations/Config.java +++ b/src/main/java/eu/siacs/conversations/Config.java @@ -202,6 +202,8 @@ public final class Config { public final static int LOCATION_FIX_SIGNIFICANT_TIME_DELTA = 1000 * 60 * 2; // ms } - // How deep nested quotes should become. '2' means one quote nested in another. + // How deep nested quotes should be displayed. '2' means one quote nested in another. public static final int QUOTE_MAX_DEPTH = 3; + // How deep nested quotes should be created on quoting a message. + public static final int QUOTING_MAX_DEPTH = QUOTE_MAX_DEPTH - 1; } diff --git a/src/main/java/eu/siacs/conversations/ui/util/QuoteHelper.java b/src/main/java/eu/siacs/conversations/ui/util/QuoteHelper.java index cfd2a2e63..cc539f1ef 100644 --- a/src/main/java/eu/siacs/conversations/ui/util/QuoteHelper.java +++ b/src/main/java/eu/siacs/conversations/ui/util/QuoteHelper.java @@ -1,5 +1,6 @@ package eu.siacs.conversations.ui.util; +import eu.siacs.conversations.Config; import eu.siacs.conversations.utils.UIHelper; public class QuoteHelper { @@ -32,20 +33,19 @@ public class QuoteHelper { } return false; } - /*public static int getQuoteColors(XmppActivity activity, boolean darkBackground, int quoteDepth){ - int[] colorsLight = R.style.ConversationsTheme_Dark; - int[] colorsDark = Config.QUOTE_COLOR_ARRAY_DARK; - Collections.rotate(Collections.singletonList(colorsLight), quoteDepth); - Collections.rotate(Collections.singletonList(colorsDark), quoteDepth); - - Arrays.stream(colorsLight).toArray(); - - int quoteColors = darkBackground ? ContextCompat.getColor(activity, colorsLight[quoteDepth-1]) - : ContextCompat.getColor(activity, colorsDark[quoteDepth-1]); - - Collections.rotate - - return quoteColors; - };*/ + public static boolean isNestedTooDeeply (CharSequence line){ + if (isPositionQuoteCharacter(line, 0)) { + int nestingDepth = 1; + for (int i = 1; i < line.length(); i++) { + if (isPositionQuoteCharacter(line, i)) { + nestingDepth++; + } + if (nestingDepth > (Config.QUOTING_MAX_DEPTH)) { + return true; + } + } + } + return false; + } } diff --git a/src/main/java/eu/siacs/conversations/ui/widget/EditMessage.java b/src/main/java/eu/siacs/conversations/ui/widget/EditMessage.java index eadd562a7..a4c6e67fd 100644 --- a/src/main/java/eu/siacs/conversations/ui/widget/EditMessage.java +++ b/src/main/java/eu/siacs/conversations/ui/widget/EditMessage.java @@ -142,7 +142,7 @@ public class EditMessage extends EmojiWrapperEditText { } public void insertAsQuote(String text) { - text = text.replaceAll("(\n *){2,}", "\n").replaceAll("(^|\n)", "$1> ").replaceAll("\n$", ""); + text = text.replaceAll("(\n *){2,}", "\n").replaceAll("(^|\n)>", "$1>>").replaceAll("(^|\n)([^>])", "$1> $2").replaceAll("\n$", ""); Editable editable = getEditableText(); int position = getSelectionEnd(); if (position == -1) position = editable.length(); diff --git a/src/main/java/eu/siacs/conversations/utils/MessageUtils.java b/src/main/java/eu/siacs/conversations/utils/MessageUtils.java index 3cb6338db..c9c029c65 100644 --- a/src/main/java/eu/siacs/conversations/utils/MessageUtils.java +++ b/src/main/java/eu/siacs/conversations/utils/MessageUtils.java @@ -39,6 +39,7 @@ import eu.siacs.conversations.entities.Conversational; import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.http.AesGcmURL; import eu.siacs.conversations.http.URL; +import eu.siacs.conversations.ui.util.QuoteHelper; public class MessageUtils { @@ -69,7 +70,7 @@ public class MessageUtils { continue; } final char c = line.charAt(0); - if (c == '>' && UIHelper.isPositionFollowedByQuoteableCharacter(line, 0) + if (QuoteHelper.isNestedTooDeeply(line) || (c == '\u00bb' && !UIHelper.isPositionFollowedByQuote(line, 0))) { continue; } From c81c8a62b3df6b8eb604ecd0d4792c37360cc1f9 Mon Sep 17 00:00:00 2001 From: Millesimus Date: Sat, 21 Aug 2021 17:21:44 +0200 Subject: [PATCH 36/82] Small refactoring for a more intuitive config. --- src/main/java/eu/siacs/conversations/Config.java | 2 +- src/main/java/eu/siacs/conversations/ui/util/QuoteHelper.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/Config.java b/src/main/java/eu/siacs/conversations/Config.java index a8494d68c..7cbe70346 100644 --- a/src/main/java/eu/siacs/conversations/Config.java +++ b/src/main/java/eu/siacs/conversations/Config.java @@ -205,5 +205,5 @@ public final class Config { // How deep nested quotes should be displayed. '2' means one quote nested in another. public static final int QUOTE_MAX_DEPTH = 3; // How deep nested quotes should be created on quoting a message. - public static final int QUOTING_MAX_DEPTH = QUOTE_MAX_DEPTH - 1; + public static final int QUOTING_MAX_DEPTH = QUOTE_MAX_DEPTH; } diff --git a/src/main/java/eu/siacs/conversations/ui/util/QuoteHelper.java b/src/main/java/eu/siacs/conversations/ui/util/QuoteHelper.java index cc539f1ef..2502f3682 100644 --- a/src/main/java/eu/siacs/conversations/ui/util/QuoteHelper.java +++ b/src/main/java/eu/siacs/conversations/ui/util/QuoteHelper.java @@ -41,7 +41,7 @@ public class QuoteHelper { if (isPositionQuoteCharacter(line, i)) { nestingDepth++; } - if (nestingDepth > (Config.QUOTING_MAX_DEPTH)) { + if (nestingDepth > (Config.QUOTING_MAX_DEPTH - 1)) { return true; } } From 3921f3a940d6e2b50f293013f42115711cec9955 Mon Sep 17 00:00:00 2001 From: Millesimus Date: Sat, 21 Aug 2021 17:25:06 +0200 Subject: [PATCH 37/82] QUOTING_MAX_DEPTH=1 for transitory compatibility with older versions. QUOTE_MAX_DEPTH=7 for performance testing and hiding of a rerendering bug occuring when two adjacent messages are merged. --- src/main/java/eu/siacs/conversations/Config.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/Config.java b/src/main/java/eu/siacs/conversations/Config.java index 7cbe70346..c89ce81bb 100644 --- a/src/main/java/eu/siacs/conversations/Config.java +++ b/src/main/java/eu/siacs/conversations/Config.java @@ -203,7 +203,7 @@ public final class Config { } // How deep nested quotes should be displayed. '2' means one quote nested in another. - public static final int QUOTE_MAX_DEPTH = 3; + public static final int QUOTE_MAX_DEPTH = 7; // How deep nested quotes should be created on quoting a message. - public static final int QUOTING_MAX_DEPTH = QUOTE_MAX_DEPTH; + public static final int QUOTING_MAX_DEPTH = 1; } From 748443cd4efc604df6c4b916d341e6ef94b44afb Mon Sep 17 00:00:00 2001 From: Millesimus Date: Sat, 21 Aug 2021 17:42:01 +0200 Subject: [PATCH 38/82] Fixing message preview. --- src/main/java/eu/siacs/conversations/utils/UIHelper.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/eu/siacs/conversations/utils/UIHelper.java b/src/main/java/eu/siacs/conversations/utils/UIHelper.java index 26e6af2fa..c293bcd12 100644 --- a/src/main/java/eu/siacs/conversations/utils/UIHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/UIHelper.java @@ -32,6 +32,7 @@ import eu.siacs.conversations.entities.Presence; import eu.siacs.conversations.entities.RtpSessionStatus; import eu.siacs.conversations.entities.Transferable; import eu.siacs.conversations.services.ExportBackupService; +import eu.siacs.conversations.ui.util.QuoteHelper; import eu.siacs.conversations.xmpp.Jid; public class UIHelper { @@ -328,7 +329,7 @@ public class UIHelper { continue; } char first = l.charAt(0); - if ((first != '>' || !isPositionFollowedByQuoteableCharacter(l, 0)) && first != '\u00bb') { + if ((!QuoteHelper.isPositionQuoteStart(l, 0)) && first != '\u00bb') { CharSequence line = CharSequenceUtils.trim(l); if (line.length() == 0) { continue; From a0bca08997cba7952cba49328ba09d71a6e8a1ba Mon Sep 17 00:00:00 2001 From: Millesimus Date: Sun, 22 Aug 2021 08:53:47 +0200 Subject: [PATCH 39/82] Rewrite QuoteHelper to integrate French quotes logics. Also reallow QuoteChars not followed by whitespace as indicated in XEP-0393. --- .../conversations/ui/util/QuoteHelper.java | 61 +++++++++++++++++-- .../siacs/conversations/utils/UIHelper.java | 35 +---------- 2 files changed, 59 insertions(+), 37 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/ui/util/QuoteHelper.java b/src/main/java/eu/siacs/conversations/ui/util/QuoteHelper.java index 2502f3682..4e4617cab 100644 --- a/src/main/java/eu/siacs/conversations/ui/util/QuoteHelper.java +++ b/src/main/java/eu/siacs/conversations/ui/util/QuoteHelper.java @@ -5,8 +5,32 @@ import eu.siacs.conversations.utils.UIHelper; public class QuoteHelper { + + public static final char QUOTE_CHAR = '>'; + public static final char QUOTE_END_CHAR = '<'; // used for one check, not for actual quoting + public static final char QUOTE_ALT_CHAR = '»'; + public static final char QUOTE_ALT_END_CHAR = '«'; + public static boolean isPositionQuoteCharacter(CharSequence body, int pos){ - return body.charAt(pos) == '>'; + // second part of logical check actually goes against the logic indicated in the method name, since it also checks for context + // but it's very useful + return body.charAt(pos) == QUOTE_CHAR || isPositionAltQuoteStart(body, pos); + } + + public static boolean isPositionQuoteEndCharacter(CharSequence body, int pos){ + return body.charAt(pos) == QUOTE_END_CHAR; + } + + public static boolean isPositionAltQuoteCharacter (CharSequence body, int pos){ + return body.charAt(pos) == QUOTE_ALT_CHAR; + } + + public static boolean isPositionAltQuoteEndCharacter(CharSequence body, int pos){ + return body.charAt(pos) == QUOTE_ALT_END_CHAR; + } + + public static boolean isPositionAltQuoteStart(CharSequence body, int pos){ + return isPositionAltQuoteCharacter(body, pos) && !isPositionFollowedByAltQuoteEnd(body, pos); } public static boolean isPositionFollowedByQuoteChar(CharSequence body, int pos) { @@ -19,10 +43,10 @@ public class QuoteHelper { } public static boolean isPositionQuoteStart (CharSequence body, int pos){ - return isPositionQuoteCharacter(body, pos) + return (isPositionQuoteCharacter(body, pos) && isPositionPrecededByPrequote(body, pos) - && (UIHelper.isPositionFollowedByWhitespace(body, pos) - || isPositionFollowedByQuoteChar(body, pos)); + && (UIHelper.isPositionFollowedByQuoteableCharacter(body, pos) + || isPositionFollowedByQuoteChar(body, pos))); } public static boolean bodyContainsQuoteStart (CharSequence body){ @@ -34,6 +58,24 @@ public class QuoteHelper { return false; } + public static boolean isPositionFollowedByAltQuoteEnd(CharSequence body, int pos) { + if (body.length() <= pos + 1 || Character.isWhitespace(body.charAt(pos + 1))) { + return false; + } + boolean previousWasWhitespace = false; + for (int i = pos + 1; i < body.length(); i++) { + char c = body.charAt(i); + if (c == '\n' || isPositionAltQuoteCharacter(body, i)) { + return false; + } else if (isPositionAltQuoteEndCharacter(body, i) && !previousWasWhitespace) { + return true; + } else { + previousWasWhitespace = Character.isWhitespace(c); + } + } + return false; + } + public static boolean isNestedTooDeeply (CharSequence line){ if (isPositionQuoteCharacter(line, 0)) { int nestingDepth = 1; @@ -48,4 +90,13 @@ public class QuoteHelper { } return false; } -} + + public static String replaceAltQuoteCharsInText(String text){ + for (int i = 0; i < text.length(); i++){ + if (isPositionAltQuoteStart(text, i)){ + text = text.substring(0, i) + QUOTE_CHAR + text.substring(i + 1); + } + } + return text; + } +} \ No newline at end of file diff --git a/src/main/java/eu/siacs/conversations/utils/UIHelper.java b/src/main/java/eu/siacs/conversations/utils/UIHelper.java index c293bcd12..4c2c0b585 100644 --- a/src/main/java/eu/siacs/conversations/utils/UIHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/UIHelper.java @@ -329,7 +329,7 @@ public class UIHelper { continue; } char first = l.charAt(0); - if ((!QuoteHelper.isPositionQuoteStart(l, 0)) && first != '\u00bb') { + if ((!QuoteHelper.isPositionQuoteStart(l, 0))) { CharSequence line = CharSequenceUtils.trim(l); if (line.length() == 0) { continue; @@ -373,14 +373,6 @@ public class UIHelper { return input.length() > 256 ? StylingHelper.subSequence(input, 0, 256) : input; } - public static boolean isPositionFollowedByWhitespace(CharSequence body, int pos){ - return Character.isWhitespace(body.charAt(pos + 1)); - } - - public static boolean isPositionPrecededByWhitespace(CharSequence body, int pos){ - return Character.isWhitespace(body.charAt(pos -1 )); - } - public static boolean isPositionPrecededByBodyStart(CharSequence body, int pos){ // true if not a single linebreak before current position for (int i = pos - 1; i >= 0; i--){ @@ -395,10 +387,7 @@ public class UIHelper { if (isPositionPrecededByBodyStart(body, pos)){ return true; } - if (body.charAt(pos - 1) == '\n'){ - return true; - } - return false; + return body.charAt(pos - 1) == '\n'; } public static boolean isPositionFollowedByQuoteableCharacter(CharSequence body, int pos) { @@ -442,31 +431,13 @@ public class UIHelper { final char c = body.charAt(i); if (Character.isWhitespace(c)) { return false; - } else if (c == '<' || c == '>') { + } else if (QuoteHelper.isPositionQuoteCharacter(body, pos) || QuoteHelper.isPositionQuoteEndCharacter(body, pos)) { return body.length() == i + 1 || Character.isWhitespace(body.charAt(i + 1)); } } return false; } - public static boolean isPositionFollowedByQuote(CharSequence body, int pos) { - if (body.length() <= pos + 1 || Character.isWhitespace(body.charAt(pos + 1))) { - return false; - } - boolean previousWasWhitespace = false; - for (int i = pos + 1; i < body.length(); i++) { - char c = body.charAt(i); - if (c == '\n' || c == '»') { - return false; - } else if (c == '«' && !previousWasWhitespace) { - return true; - } else { - previousWasWhitespace = Character.isWhitespace(c); - } - } - return false; - } - public static String getDisplayName(MucOptions.User user) { Contact contact = user.getContact(); if (contact != null) { From 2db2ca95ceef7f1a50d50097f95b0d9efd9a79b6 Mon Sep 17 00:00:00 2001 From: Millesimus Date: Sun, 22 Aug 2021 08:55:46 +0200 Subject: [PATCH 40/82] Move differentiation between XEP-0393 quotes and French quotes to QuoteHelper. --- .../eu/siacs/conversations/ui/adapter/MessageAdapter.java | 5 ++--- src/main/java/eu/siacs/conversations/utils/MessageUtils.java | 3 +-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java index 5c9104343..dd0bdc589 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -371,9 +371,8 @@ public class MessageAdapter extends ArrayAdapter { if (lineStart == -1) { if (previous == '\n') { if ( - (QuoteHelper.isPositionQuoteStart(body, i) - || (current == '\u00bb' && !UIHelper.isPositionFollowedByQuote(body, i) - ))) { + QuoteHelper.isPositionQuoteStart(body, i) + ) { // Line start with quote lineStart = i; if (quoteStart == -1) quoteStart = i; diff --git a/src/main/java/eu/siacs/conversations/utils/MessageUtils.java b/src/main/java/eu/siacs/conversations/utils/MessageUtils.java index c9c029c65..0a11cd720 100644 --- a/src/main/java/eu/siacs/conversations/utils/MessageUtils.java +++ b/src/main/java/eu/siacs/conversations/utils/MessageUtils.java @@ -70,8 +70,7 @@ public class MessageUtils { continue; } final char c = line.charAt(0); - if (QuoteHelper.isNestedTooDeeply(line) - || (c == '\u00bb' && !UIHelper.isPositionFollowedByQuote(line, 0))) { + if (QuoteHelper.isNestedTooDeeply(line)) { continue; } if (builder.length() != 0) { From a0529a4e1e79734e41e3bbbf630d7460fea24286 Mon Sep 17 00:00:00 2001 From: Millesimus Date: Sun, 22 Aug 2021 08:56:32 +0200 Subject: [PATCH 41/82] On quoting, translate French quotes to XEP-0393 quotes. --- .../java/eu/siacs/conversations/ui/widget/EditMessage.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/eu/siacs/conversations/ui/widget/EditMessage.java b/src/main/java/eu/siacs/conversations/ui/widget/EditMessage.java index a4c6e67fd..3471ffcfa 100644 --- a/src/main/java/eu/siacs/conversations/ui/widget/EditMessage.java +++ b/src/main/java/eu/siacs/conversations/ui/widget/EditMessage.java @@ -24,6 +24,7 @@ import java.util.concurrent.Executors; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; +import eu.siacs.conversations.ui.util.QuoteHelper; public class EditMessage extends EmojiWrapperEditText { @@ -142,7 +143,8 @@ public class EditMessage extends EmojiWrapperEditText { } public void insertAsQuote(String text) { - text = text.replaceAll("(\n *){2,}", "\n").replaceAll("(^|\n)>", "$1>>").replaceAll("(^|\n)([^>])", "$1> $2").replaceAll("\n$", ""); + text = QuoteHelper.replaceAltQuoteCharsInText(text); + text = text.replaceAll("(\n *){2,}", "\n").replaceAll("(^|\n)(" + QuoteHelper.QUOTE_CHAR + ")", "$1$2$2").replaceAll("(^|\n)([^>" + QuoteHelper.QUOTE_CHAR + "])", "$1> $2").replaceAll("\n$", ""); Editable editable = getEditableText(); int position = getSelectionEnd(); if (position == -1) position = editable.length(); From 955a6f3fe13e0e2783ba529bcb31dcb413d1568b Mon Sep 17 00:00:00 2001 From: Millesimus Date: Sun, 22 Aug 2021 17:45:23 +0200 Subject: [PATCH 42/82] Bugfix for 6cc06bcb98acc05c7677c642adf8ded90ffc8372. --- .../java/eu/siacs/conversations/ui/adapter/MessageAdapter.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java index dd0bdc589..0eb8a10fb 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java @@ -381,7 +381,6 @@ public class MessageAdapter extends ArrayAdapter { // Line start without quote, apply spans there applyQuoteSpan(body, quoteStart, i - 1, darkBackground); quoteStart = -1; - quoteDepth++; } } } else { @@ -406,8 +405,8 @@ public class MessageAdapter extends ArrayAdapter { if (quoteStart >= 0) { // Apply spans to finishing open quote applyQuoteSpan(body, quoteStart, body.length(), darkBackground); - quoteDepth++; } + quoteDepth++; } return startsWithQuote; } From b6fe1898e7717af67bfc98fe6f05dc4a44820e28 Mon Sep 17 00:00:00 2001 From: Millesimus Date: Mon, 23 Aug 2021 13:09:21 +0200 Subject: [PATCH 43/82] Minor duplication fix. --- src/main/java/eu/siacs/conversations/ui/widget/EditMessage.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/eu/siacs/conversations/ui/widget/EditMessage.java b/src/main/java/eu/siacs/conversations/ui/widget/EditMessage.java index 3471ffcfa..eba833c9b 100644 --- a/src/main/java/eu/siacs/conversations/ui/widget/EditMessage.java +++ b/src/main/java/eu/siacs/conversations/ui/widget/EditMessage.java @@ -144,7 +144,7 @@ public class EditMessage extends EmojiWrapperEditText { public void insertAsQuote(String text) { text = QuoteHelper.replaceAltQuoteCharsInText(text); - text = text.replaceAll("(\n *){2,}", "\n").replaceAll("(^|\n)(" + QuoteHelper.QUOTE_CHAR + ")", "$1$2$2").replaceAll("(^|\n)([^>" + QuoteHelper.QUOTE_CHAR + "])", "$1> $2").replaceAll("\n$", ""); + text = text.replaceAll("(\n *){2,}", "\n").replaceAll("(^|\n)(" + QuoteHelper.QUOTE_CHAR + ")", "$1$2$2").replaceAll("(^|\n)([^" + QuoteHelper.QUOTE_CHAR + "])", "$1> $2").replaceAll("\n$", ""); Editable editable = getEditableText(); int position = getSelectionEnd(); if (position == -1) position = editable.length(); From ef8f10cc1327c839b24d56d0db30ea25311b5e76 Mon Sep 17 00:00:00 2001 From: Maximilian Weiler <16721506+maweil@users.noreply.github.com> Date: Tue, 27 Jul 2021 21:27:09 +0200 Subject: [PATCH 44/82] Optionally prevent taking screenshots - Add setting to prevent screenshots - Enforce using FLAG_SECURE in onResume for each activity --- .../siacs/conversations/ui/AboutActivity.java | 8 ++++++++ .../ui/ConversationActivity.java | 9 +++++++++ .../conversations/ui/LocationActivity.java | 3 +++ .../conversations/ui/MemorizingActivity.java | 4 ++++ .../conversations/ui/RecordingActivity.java | 8 ++++++++ .../siacs/conversations/ui/ScanActivity.java | 3 +++ .../conversations/ui/SettingsActivity.java | 10 ++++++++++ .../siacs/conversations/ui/XmppActivity.java | 4 +++- .../conversations/ui/util/SettingsUtils.java | 20 +++++++++++++++++++ src/main/res/values/defaults.xml | 1 + src/main/res/values/strings.xml | 3 +++ src/main/res/xml/preferences.xml | 7 +++++++ 12 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 src/main/java/eu/siacs/conversations/ui/util/SettingsUtils.java diff --git a/src/main/java/eu/siacs/conversations/ui/AboutActivity.java b/src/main/java/eu/siacs/conversations/ui/AboutActivity.java index 3c705e639..f79a65597 100644 --- a/src/main/java/eu/siacs/conversations/ui/AboutActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/AboutActivity.java @@ -1,16 +1,24 @@ package eu.siacs.conversations.ui; import android.os.Bundle; +import android.preference.PreferenceManager; import androidx.appcompat.app.AppCompatActivity; import eu.siacs.conversations.R; +import eu.siacs.conversations.ui.util.SettingsUtils; import eu.siacs.conversations.utils.ThemeHelper; import static eu.siacs.conversations.ui.XmppActivity.configureActionBar; public class AboutActivity extends AppCompatActivity { + @Override + protected void onResume(){ + super.onResume(); + SettingsUtils.applyScreenshotPreventionSetting(this); + } + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java index 8e23da5bc..04361a5da 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java @@ -2,10 +2,13 @@ package eu.siacs.conversations.ui; import android.content.Intent; import android.os.Bundle; +import android.preference.PreferenceManager; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; +import eu.siacs.conversations.ui.util.SettingsUtils; + public class ConversationActivity extends AppCompatActivity { @Override @@ -14,4 +17,10 @@ public class ConversationActivity extends AppCompatActivity { startActivity(new Intent(this, ConversationsActivity.class)); finish(); } + + @Override + protected void onResume(){ + super.onResume(); + SettingsUtils.applyScreenshotPreventionSetting(this); + } } diff --git a/src/main/java/eu/siacs/conversations/ui/LocationActivity.java b/src/main/java/eu/siacs/conversations/ui/LocationActivity.java index ed08fa0d2..2627e0e59 100644 --- a/src/main/java/eu/siacs/conversations/ui/LocationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/LocationActivity.java @@ -39,6 +39,7 @@ import eu.siacs.conversations.services.QuickConversationsService; import eu.siacs.conversations.ui.util.LocationHelper; import eu.siacs.conversations.ui.widget.Marker; import eu.siacs.conversations.ui.widget.MyLocation; +import eu.siacs.conversations.ui.util.SettingsUtils; import eu.siacs.conversations.utils.ThemeHelper; public abstract class LocationActivity extends ActionBarActivity implements LocationListener { @@ -68,6 +69,7 @@ public abstract class LocationActivity extends ActionBarActivity implements Loca } } + protected void updateLocationMarkers() { clearMarkers(); } @@ -222,6 +224,7 @@ public abstract class LocationActivity extends ActionBarActivity implements Loca @Override protected void onResume() { super.onResume(); + SettingsUtils.applyScreenshotPreventionSetting(this); Configuration.getInstance().load(this, getPreferences()); map.onResume(); this.setMyLoc(null); diff --git a/src/main/java/eu/siacs/conversations/ui/MemorizingActivity.java b/src/main/java/eu/siacs/conversations/ui/MemorizingActivity.java index 7f8a55a72..123f57fbb 100644 --- a/src/main/java/eu/siacs/conversations/ui/MemorizingActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/MemorizingActivity.java @@ -29,6 +29,7 @@ import android.content.DialogInterface.OnCancelListener; import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.os.Bundle; +import android.preference.PreferenceManager; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; @@ -39,6 +40,7 @@ import java.util.logging.Logger; import eu.siacs.conversations.R; import eu.siacs.conversations.entities.MTMDecision; import eu.siacs.conversations.services.MemorizingTrustManager; +import eu.siacs.conversations.ui.util.SettingsUtils; import eu.siacs.conversations.utils.ThemeHelper; public class MemorizingActivity extends AppCompatActivity implements OnClickListener, OnCancelListener { @@ -61,6 +63,8 @@ public class MemorizingActivity extends AppCompatActivity implements OnClickList @Override public void onResume() { super.onResume(); + SettingsUtils.applyScreenshotPreventionSetting(this); + Intent i = getIntent(); decisionId = i.getIntExtra(MemorizingTrustManager.DECISION_INTENT_ID, MTMDecision.DECISION_INVALID); int titleId = i.getIntExtra(MemorizingTrustManager.DECISION_TITLE_ID, R.string.mtm_accept_cert); diff --git a/src/main/java/eu/siacs/conversations/ui/RecordingActivity.java b/src/main/java/eu/siacs/conversations/ui/RecordingActivity.java index 5e4c90048..aa4e15c06 100644 --- a/src/main/java/eu/siacs/conversations/ui/RecordingActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/RecordingActivity.java @@ -9,6 +9,7 @@ import android.os.Bundle; import android.os.FileObserver; import android.os.Handler; import android.os.SystemClock; +import android.preference.PreferenceManager; import android.util.Log; import android.view.View; import android.view.WindowManager; @@ -28,6 +29,7 @@ import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.ActivityRecordingBinding; import eu.siacs.conversations.persistance.FileBackend; +import eu.siacs.conversations.ui.util.SettingsUtils; import eu.siacs.conversations.utils.ThemeHelper; import eu.siacs.conversations.utils.TimeFrameUtils; @@ -66,6 +68,12 @@ public class RecordingActivity extends Activity implements View.OnClickListener getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } + @Override + protected void onResume(){ + super.onResume(); + SettingsUtils.applyScreenshotPreventionSetting(this); + } + @Override protected void onStart() { super.onStart(); diff --git a/src/main/java/eu/siacs/conversations/ui/ScanActivity.java b/src/main/java/eu/siacs/conversations/ui/ScanActivity.java index cebd19bc3..3e7eddc64 100644 --- a/src/main/java/eu/siacs/conversations/ui/ScanActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ScanActivity.java @@ -33,6 +33,7 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.Process; import android.os.Vibrator; +import android.preference.PreferenceManager; import android.util.Log; import android.view.KeyEvent; import android.view.Surface; @@ -61,6 +62,7 @@ import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.ui.service.CameraManager; import eu.siacs.conversations.ui.widget.ScannerView; +import eu.siacs.conversations.ui.util.SettingsUtils; /** * @author Andreas Schildbach @@ -181,6 +183,7 @@ public final class ScanActivity extends Activity implements SurfaceTextureListen @Override protected void onResume() { super.onResume(); + SettingsUtils.applyScreenshotPreventionSetting(this); maybeOpenCamera(); } diff --git a/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java b/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java index 68cc42921..7f4e59d1a 100644 --- a/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java @@ -40,6 +40,7 @@ import eu.siacs.conversations.services.MemorizingTrustManager; import eu.siacs.conversations.services.QuickConversationsService; import eu.siacs.conversations.ui.util.StyledAttributes; import eu.siacs.conversations.utils.GeoHelper; +import eu.siacs.conversations.ui.util.SettingsUtils; import eu.siacs.conversations.utils.TimeFrameUtils; import eu.siacs.conversations.xmpp.Jid; @@ -57,8 +58,10 @@ public class SettingsActivity extends XmppActivity implements public static final String THEME = "theme"; public static final String SHOW_DYNAMIC_TAGS = "show_dynamic_tags"; public static final String OMEMO_SETTING = "omemo"; + public static final String PREVENT_SCREENSHOTS = "prevent_screenshots"; public static final int REQUEST_CREATE_BACKUP = 0xbf8701; + private SettingsFragment mSettingsFragment; @Override @@ -393,8 +396,15 @@ public class SettingsActivity extends XmppActivity implements if (this.mTheme != theme) { recreate(); } + } else if(name.equals(PREVENT_SCREENSHOTS)){ + SettingsUtils.applyScreenshotPreventionSetting(this); } + } + @Override + public void onResume(){ + super.onResume(); + SettingsUtils.applyScreenshotPreventionSetting(this); } @Override diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java index 021ec4a5e..4b5382b44 100644 --- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java @@ -80,6 +80,7 @@ import eu.siacs.conversations.ui.util.PresenceSelector; import eu.siacs.conversations.ui.util.SoftKeyboardUtils; import eu.siacs.conversations.utils.AccountUtils; import eu.siacs.conversations.utils.ExceptionHelper; +import eu.siacs.conversations.ui.util.SettingsUtils; import eu.siacs.conversations.utils.ThemeHelper; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.OnKeyStatusUpdated; @@ -819,8 +820,9 @@ public abstract class XmppActivity extends ActionBarActivity { } @Override - public void onResume() { + protected void onResume(){ super.onResume(); + SettingsUtils.applyScreenshotPreventionSetting(this); } protected int findTheme() { diff --git a/src/main/java/eu/siacs/conversations/ui/util/SettingsUtils.java b/src/main/java/eu/siacs/conversations/ui/util/SettingsUtils.java new file mode 100644 index 000000000..ae99e0943 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/ui/util/SettingsUtils.java @@ -0,0 +1,20 @@ +package eu.siacs.conversations.ui.util; + +import android.app.Activity; +import android.content.SharedPreferences; +import android.preference.PreferenceManager; +import android.view.Window; +import android.view.WindowManager; + +public class SettingsUtils { + public static void applyScreenshotPreventionSetting(Activity activity){ + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(activity); + boolean preventScreenshots = preferences.getBoolean("prevent_screenshots", false); + Window activityWindow = activity.getWindow(); + if(preventScreenshots){ + activityWindow.addFlags(WindowManager.LayoutParams.FLAG_SECURE); + } else { + activityWindow.clearFlags(WindowManager.LayoutParams.FLAG_SECURE); + } + } +} diff --git a/src/main/res/values/defaults.xml b/src/main/res/values/defaults.xml index a031a9149..60085d0f9 100644 --- a/src/main/res/values/defaults.xml +++ b/src/main/res/values/defaults.xml @@ -44,4 +44,5 @@ false 360 JABBER_NETWORK + false diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 9f3ba84ac..5d8c32e54 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -130,6 +130,8 @@ By sending in stack traces you are helping the development Confirm Messages Let your contacts know when you have received and read their messages + Prevent Screenshots + Prevent taking screenshots of this app and hide its content in the app switcher UI OpenKeychain produced an error. Bad key for encryption. @@ -966,4 +968,5 @@ The backup has been started. You’ll get a notification once it has been completed. Unable to enable video. Plain text document + diff --git a/src/main/res/xml/preferences.xml b/src/main/res/xml/preferences.xml index 99f9e83ab..91b07210c 100644 --- a/src/main/res/xml/preferences.xml +++ b/src/main/res/xml/preferences.xml @@ -32,6 +32,13 @@ android:key="last_activity" android:summary="@string/pref_broadcast_last_activity_summary" android:title="@string/pref_broadcast_last_activity" /> + + + Date: Tue, 27 Jul 2021 21:51:55 +0200 Subject: [PATCH 45/82] Remove unused import --- src/main/java/eu/siacs/conversations/ui/AboutActivity.java | 1 - .../java/eu/siacs/conversations/ui/ConversationActivity.java | 1 - .../java/eu/siacs/conversations/ui/MemorizingActivity.java | 1 - src/main/java/eu/siacs/conversations/ui/RecordingActivity.java | 1 - src/main/java/eu/siacs/conversations/ui/ScanActivity.java | 3 +-- 5 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/ui/AboutActivity.java b/src/main/java/eu/siacs/conversations/ui/AboutActivity.java index f79a65597..917512a02 100644 --- a/src/main/java/eu/siacs/conversations/ui/AboutActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/AboutActivity.java @@ -1,7 +1,6 @@ package eu.siacs.conversations.ui; import android.os.Bundle; -import android.preference.PreferenceManager; import androidx.appcompat.app.AppCompatActivity; diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java index 04361a5da..2d9fe91ae 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java @@ -2,7 +2,6 @@ package eu.siacs.conversations.ui; import android.content.Intent; import android.os.Bundle; -import android.preference.PreferenceManager; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; diff --git a/src/main/java/eu/siacs/conversations/ui/MemorizingActivity.java b/src/main/java/eu/siacs/conversations/ui/MemorizingActivity.java index 123f57fbb..23f3c82d8 100644 --- a/src/main/java/eu/siacs/conversations/ui/MemorizingActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/MemorizingActivity.java @@ -29,7 +29,6 @@ import android.content.DialogInterface.OnCancelListener; import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.os.Bundle; -import android.preference.PreferenceManager; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; diff --git a/src/main/java/eu/siacs/conversations/ui/RecordingActivity.java b/src/main/java/eu/siacs/conversations/ui/RecordingActivity.java index aa4e15c06..6146c4ae7 100644 --- a/src/main/java/eu/siacs/conversations/ui/RecordingActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/RecordingActivity.java @@ -9,7 +9,6 @@ import android.os.Bundle; import android.os.FileObserver; import android.os.Handler; import android.os.SystemClock; -import android.preference.PreferenceManager; import android.util.Log; import android.view.View; import android.view.WindowManager; diff --git a/src/main/java/eu/siacs/conversations/ui/ScanActivity.java b/src/main/java/eu/siacs/conversations/ui/ScanActivity.java index 3e7eddc64..95505647d 100644 --- a/src/main/java/eu/siacs/conversations/ui/ScanActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ScanActivity.java @@ -33,7 +33,6 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.Process; import android.os.Vibrator; -import android.preference.PreferenceManager; import android.util.Log; import android.view.KeyEvent; import android.view.Surface; @@ -61,8 +60,8 @@ import java.util.Map; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.ui.service.CameraManager; -import eu.siacs.conversations.ui.widget.ScannerView; import eu.siacs.conversations.ui.util.SettingsUtils; +import eu.siacs.conversations.ui.widget.ScannerView; /** * @author Andreas Schildbach From b00b8996d5fd8cec3cdee31732ce52ac7879e35e Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 24 Aug 2021 10:13:03 +0200 Subject: [PATCH 46/82] bump gradle version and agp --- build.gradle | 4 ++-- gradle/wrapper/gradle-wrapper.properties | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/build.gradle b/build.gradle index 855500be6..3f5a2f359 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:4.2.1' + classpath 'com.android.tools.build:gradle:7.0.1' } } @@ -111,7 +111,7 @@ android { } configurations { - compile.exclude group: 'org.jetbrains' , module:'annotations' + implementation.exclude group: 'org.jetbrains' , module:'annotations' } dataBinding { diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index ea1e4b836..84fa7550a 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip From 339ee8f6ea4c213cfcacd47a6410fbcc0f727a81 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 24 Aug 2021 11:15:50 +0200 Subject: [PATCH 47/82] bump libwebrtc version to m92 --- .travis.yml | 22 ---------------------- build.gradle | 2 +- 2 files changed, 1 insertion(+), 23 deletions(-) delete mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 664d3cf55..000000000 --- a/.travis.yml +++ /dev/null @@ -1,22 +0,0 @@ -language: android -jdk: - - oraclejdk8 -android: - components: - - platform-tools - - tools - - build-tools-28.0.3 - - extra-google-google_play_services - licenses: - - '.+' -before_script: - - mkdir libs - - wget -O libs/libwebrtc-m90.aar https://gultsch.de/files/libwebrtc-m90.aar -script: - - ./gradlew assembleQuicksyFreeCompatDebug - - ./gradlew assembleQuicksyFreeSystemDebug - - ./gradlew assembleConversationsFreeCompatDebug - - ./gradlew assembleConversationsFreeSystemDebug - -before_install: - - yes | sdkmanager "platforms;android-28" diff --git a/build.gradle b/build.gradle index 3f5a2f359..9937179e8 100644 --- a/build.gradle +++ b/build.gradle @@ -78,7 +78,7 @@ dependencies { implementation 'com.google.guava:guava:30.1.1-android' quicksyImplementation 'io.michaelrocks:libphonenumber-android:8.12.18' - implementation fileTree(include: ['libwebrtc-m90.aar'], dir: 'libs') + implementation fileTree(include: ['libwebrtc-m92.aar'], dir: 'libs') } ext { From caefec2fbfe87465e55fc36e9d881d9bcd9c7ea5 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 24 Aug 2021 09:19:08 +0000 Subject: [PATCH 48/82] Create android.yml --- .github/workflows/android.yml | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .github/workflows/android.yml diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml new file mode 100644 index 000000000..97859f167 --- /dev/null +++ b/.github/workflows/android.yml @@ -0,0 +1,32 @@ +name: Android CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: set up JDK 11 + uses: actions/setup-java@v2 + with: + java-version: '11' + distribution: 'adopt' + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + - name: Build Quicksy (Compat) + run: ./gradlew assembleQuicksyFreeCompatDebug + - name: Build Quicksy (System) + run: ./gradlew assembleQuicksyFreeSystemDebug + - name: Build Conversations (Compat) + run: ./gradlew assembleConversationsFreeCompatDebug + - name: Build Conversations (System) + run: ./gradlew assembleConversationsFreeSystemDebug + From 4e90c0dbbb090dd014a10601caca246d4dca219a Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 24 Aug 2021 09:21:17 +0000 Subject: [PATCH 49/82] Update android.yml to download webrtc --- .github/workflows/android.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 97859f167..2c0861ffb 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -18,7 +18,8 @@ jobs: with: java-version: '11' distribution: 'adopt' - + - name: Download WebRTC + run: mkdir libs && wget -O libs/libwebrtc-m92.aar https://gultsch.de/files/libwebrtc-m92.aar - name: Grant execute permission for gradlew run: chmod +x gradlew - name: Build Quicksy (Compat) From bf3c1d573be81e7646d56f435c9a4640dcdeb323 Mon Sep 17 00:00:00 2001 From: Licaon_Kter Date: Tue, 24 Aug 2021 10:15:13 +0000 Subject: [PATCH 50/82] Avoid description repetition screenshots --- src/main/res/values/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 5d8c32e54..6a3815073 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -131,7 +131,7 @@ Confirm Messages Let your contacts know when you have received and read their messages Prevent Screenshots - Prevent taking screenshots of this app and hide its content in the app switcher + Hide app contents in the app switcher and block screenshots UI OpenKeychain produced an error. Bad key for encryption. From 208c9d91db5cace8641db21dbc0bf38fb4574096 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 24 Aug 2021 13:02:31 +0200 Subject: [PATCH 51/82] dexOptions is no longer used in agp7 --- build.gradle | 6 ------ 1 file changed, 6 deletions(-) diff --git a/build.gradle b/build.gradle index 9937179e8..7720d848a 100644 --- a/build.gradle +++ b/build.gradle @@ -118,12 +118,6 @@ android { enabled true } - dexOptions { - // Skip pre-dexing when running on Travis CI or when disabled via -Dpre-dex=false. - preDexLibraries = preDexEnabled && !travisBuild - jumboMode true - } - compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 From e6d8bee035d4ad86edf2589349d42e0e6594b602 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 24 Aug 2021 13:33:19 +0200 Subject: [PATCH 52/82] stop agp7 complaining about missing proguard rules --- proguard-rules.pro | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/proguard-rules.pro b/proguard-rules.pro index c8b4089c5..7e4d7d31d 100644 --- a/proguard-rules.pro +++ b/proguard-rules.pro @@ -26,6 +26,15 @@ -dontwarn java.lang.** -dontwarn javax.lang.** +-dontwarn com.android.org.conscrypt.SSLParametersImpl +-dontwarn org.apache.harmony.xnet.provider.jsse.SSLParametersImpl +-dontwarn org.bouncycastle.jsse.BCSSLParameters +-dontwarn org.bouncycastle.jsse.BCSSLSocket +-dontwarn org.bouncycastle.jsse.provider.BouncyCastleJsseProvider +-dontwarn org.openjsse.javax.net.ssl.SSLParameters +-dontwarn org.openjsse.javax.net.ssl.SSLSocket +-dontwarn org.openjsse.net.ssl.OpenJSSE + -keepclassmembers class eu.siacs.conversations.http.services.** { !transient ; } From 88d7ddf1248909f2b2dc940fee7f48342d650ae6 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 24 Aug 2021 14:40:12 +0200 Subject: [PATCH 53/82] PIP aspect ratio should match video aspect ratio. fixes #4077 --- .../conversations/ui/RtpSessionActivity.java | 27 +++++++++-- .../conversations/ui/util/Rationals.java | 26 ++++++++++ .../ui/widget/SurfaceViewRenderer.java | 48 +++++++++++++++++++ src/main/res/layout/activity_rtp_session.xml | 4 +- 4 files changed, 98 insertions(+), 7 deletions(-) create mode 100644 src/main/java/eu/siacs/conversations/ui/util/Rationals.java create mode 100644 src/main/java/eu/siacs/conversations/ui/widget/SurfaceViewRenderer.java diff --git a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java index ad016a3b3..96aa00db0 100644 --- a/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/RtpSessionActivity.java @@ -1,5 +1,8 @@ package eu.siacs.conversations.ui; +import static java.util.Arrays.asList; +import static eu.siacs.conversations.utils.PermissionUtils.getFirstDenied; + import android.Manifest; import android.annotation.SuppressLint; import android.app.PictureInPictureParams; @@ -55,6 +58,7 @@ import eu.siacs.conversations.services.AppRTCAudioManager; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.ui.util.AvatarWorkerTask; import eu.siacs.conversations.ui.util.MainThreadExecutor; +import eu.siacs.conversations.ui.util.Rationals; import eu.siacs.conversations.utils.PermissionUtils; import eu.siacs.conversations.utils.TimeFrameUtils; import eu.siacs.conversations.xml.Namespace; @@ -65,10 +69,7 @@ import eu.siacs.conversations.xmpp.jingle.JingleRtpConnection; import eu.siacs.conversations.xmpp.jingle.Media; import eu.siacs.conversations.xmpp.jingle.RtpEndUserState; -import static eu.siacs.conversations.utils.PermissionUtils.getFirstDenied; -import static java.util.Arrays.asList; - -public class RtpSessionActivity extends XmppActivity implements XmppConnectionService.OnJingleRtpConnectionUpdate { +public class RtpSessionActivity extends XmppActivity implements XmppConnectionService.OnJingleRtpConnectionUpdate, eu.siacs.conversations.ui.widget.SurfaceViewRenderer.OnAspectRatioChanged { public static final String EXTRA_WITH = "with"; public static final String EXTRA_SESSION_ID = "session_id"; @@ -446,12 +447,14 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe public void onStart() { super.onStart(); mHandler.postDelayed(mTickExecutor, CALL_DURATION_UPDATE_INTERVAL); + this.binding.remoteVideo.setOnAspectRatioChanged(this); } @Override public void onStop() { mHandler.removeCallbacks(mTickExecutor); binding.remoteVideo.release(); + binding.remoteVideo.setOnAspectRatioChanged(null); binding.localVideo.release(); final WeakReference weakReference = this.rtpConnectionReference; final JingleRtpConnection jingleRtpConnection = weakReference == null ? null : weakReference.get(); @@ -515,9 +518,12 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe @RequiresApi(api = Build.VERSION_CODES.O) private void startPictureInPicture() { try { + final Rational rational = this.binding.remoteVideo.getAspectRatio(); + final Rational clippedRational = Rationals.clip(rational); + Log.d(Config.LOGTAG, "suggested rational " + rational + ". clipped to " + clippedRational); enterPictureInPictureMode( new PictureInPictureParams.Builder() - .setAspectRatio(new Rational(10, 16)) + .setAspectRatio(clippedRational) .build() ); } catch (final IllegalStateException e) { @@ -526,6 +532,17 @@ public class RtpSessionActivity extends XmppActivity implements XmppConnectionSe } } + @Override + public void onAspectRatioChanged(final Rational rational) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && isPictureInPicture()) { + final Rational clippedRational = Rationals.clip(rational); + Log.d(Config.LOGTAG, "suggested rational after aspect ratio change " + rational + ". clipped to " + clippedRational); + setPictureInPictureParams(new PictureInPictureParams.Builder() + .setAspectRatio(clippedRational) + .build()); + } + } + private boolean deviceSupportsPictureInPicture() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { return getPackageManager().hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE); diff --git a/src/main/java/eu/siacs/conversations/ui/util/Rationals.java b/src/main/java/eu/siacs/conversations/ui/util/Rationals.java new file mode 100644 index 000000000..31155cd6e --- /dev/null +++ b/src/main/java/eu/siacs/conversations/ui/util/Rationals.java @@ -0,0 +1,26 @@ +package eu.siacs.conversations.ui.util; + +import android.util.Rational; + +public final class Rationals { + + //between 2.39:1 and 1:2.39 (inclusive). + private static final Rational MIN = new Rational(100,239); + private static final Rational MAX = new Rational(239,100); + + private Rationals() { + + } + + + public static Rational clip(final Rational input) { + if (input.compareTo(MIN) < 0) { + return MIN; + } + if (input.compareTo(MAX) > 0) { + return MAX; + } + return input; + } + +} diff --git a/src/main/java/eu/siacs/conversations/ui/widget/SurfaceViewRenderer.java b/src/main/java/eu/siacs/conversations/ui/widget/SurfaceViewRenderer.java new file mode 100644 index 000000000..06f604076 --- /dev/null +++ b/src/main/java/eu/siacs/conversations/ui/widget/SurfaceViewRenderer.java @@ -0,0 +1,48 @@ +package eu.siacs.conversations.ui.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.util.Log; +import android.util.Rational; + +import eu.siacs.conversations.Config; + +public class SurfaceViewRenderer extends org.webrtc.SurfaceViewRenderer { + + private Rational aspectRatio = new Rational(1,1); + + private OnAspectRatioChanged onAspectRatioChanged; + + public SurfaceViewRenderer(Context context) { + super(context); + } + + public SurfaceViewRenderer(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public void onFrameResolutionChanged(int videoWidth, int videoHeight, int rotation) { + super.onFrameResolutionChanged(videoWidth, videoHeight, rotation); + final int rotatedWidth = rotation != 0 && rotation != 180 ? videoHeight : videoWidth; + final int rotatedHeight = rotation != 0 && rotation != 180 ? videoWidth : videoHeight; + final Rational currentRational = this.aspectRatio; + this.aspectRatio = new Rational(rotatedWidth, rotatedHeight); + Log.d(Config.LOGTAG,"onFrameResolutionChanged("+rotatedWidth+","+rotatedHeight+","+aspectRatio+")"); + if (currentRational.equals(this.aspectRatio) || onAspectRatioChanged == null) { + return; + } + onAspectRatioChanged.onAspectRatioChanged(this.aspectRatio); + } + + public void setOnAspectRatioChanged(final OnAspectRatioChanged onAspectRatioChanged) { + this.onAspectRatioChanged = onAspectRatioChanged; + } + + public Rational getAspectRatio() { + return this.aspectRatio; + } + + public interface OnAspectRatioChanged { + void onAspectRatioChanged(final Rational rational); + } +} diff --git a/src/main/res/layout/activity_rtp_session.xml b/src/main/res/layout/activity_rtp_session.xml index 26fa4d496..0bdca4776 100644 --- a/src/main/res/layout/activity_rtp_session.xml +++ b/src/main/res/layout/activity_rtp_session.xml @@ -98,13 +98,13 @@ android:gravity="center" android:visibility="gone"> - - Date: Tue, 24 Aug 2021 14:42:50 +0200 Subject: [PATCH 54/82] reset affiliation when inviting someone not currently in group. fixes #4146 --- .../services/XmppConnectionService.java | 42 ++++++++----------- 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 759a0d727..be6c6ce01 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -471,7 +471,6 @@ public class XmppConnectionService extends Service { private OpenPgpServiceConnection pgpServiceConnection; private PgpEngine mPgpEngine = null; private WakeLock wakeLock; - private PowerManager pm; private LruCache mBitmapCache; private final BroadcastReceiver mInternalEventReceiver = new InternalEventReceiver(); private final BroadcastReceiver mInternalScreenEventReceiver = new InternalEventReceiver(); @@ -1172,7 +1171,7 @@ public class XmppConnectionService extends Service { this.pgpServiceConnection.bindToService(); } - this.pm = (PowerManager) getSystemService(Context.POWER_SERVICE); + final PowerManager pm = ContextCompat.getSystemService(this, PowerManager.class); this.wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Conversations:Service"); toggleForegroundService(); @@ -3337,35 +3336,26 @@ public class XmppConnectionService extends Service { public void changeAffiliationInConference(final Conversation conference, Jid user, final MucOptions.Affiliation affiliation, final OnAffiliationChanged callback) { final Jid jid = user.asBareJid(); - IqPacket request = this.mIqGenerator.changeAffiliation(conference, jid, affiliation.toString()); - sendIqPacket(conference.getAccount(), request, new OnIqPacketReceived() { - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() == IqPacket.TYPE.RESULT) { - conference.getMucOptions().changeAffiliation(jid, affiliation); - getAvatarService().clear(conference); + final IqPacket request = this.mIqGenerator.changeAffiliation(conference, jid, affiliation.toString()); + sendIqPacket(conference.getAccount(), request, (account, response) -> { + if (response.getType() == IqPacket.TYPE.RESULT) { + conference.getMucOptions().changeAffiliation(jid, affiliation); + getAvatarService().clear(conference); + if (callback != null) { callback.onAffiliationChangedSuccessful(jid); } else { - callback.onAffiliationChangeFailed(jid, R.string.could_not_change_affiliation); + Log.d(Config.LOGTAG, "changed affiliation of " + user + " to " + affiliation); } + } else if (callback != null) { + callback.onAffiliationChangeFailed(jid, R.string.could_not_change_affiliation); + } else { + Log.d(Config.LOGTAG, "unable to change affiliation"); } }); } - public void changeAffiliationsInConference(final Conversation conference, MucOptions.Affiliation before, MucOptions.Affiliation after) { - List jids = new ArrayList<>(); - for (MucOptions.User user : conference.getMucOptions().getUsers()) { - if (user.getAffiliation() == before && user.getRealJid() != null) { - jids.add(user.getRealJid()); - } - } - IqPacket request = this.mIqGenerator.changeAffiliation(conference, jids, after.toString()); - sendIqPacket(conference.getAccount(), request, mDefaultIqHandler); - } - public void changeRoleInConference(final Conversation conference, final String nick, MucOptions.Role role) { IqPacket request = this.mIqGenerator.changeRole(conference, nick, role.toString()); - Log.d(Config.LOGTAG, request.toString()); sendIqPacket(conference.getAccount(), request, (account, packet) -> { if (packet.getType() != IqPacket.TYPE.RESULT) { Log.d(Config.LOGTAG, account.getJid().asBareJid() + " unable to change role of " + nick); @@ -3928,9 +3918,13 @@ public class XmppConnectionService extends Service { new Thread(() -> reconnectAccount(account, false, true)).start(); } - public void invite(Conversation conversation, Jid contact) { + public void invite(final Conversation conversation, final Jid contact) { Log.d(Config.LOGTAG, conversation.getAccount().getJid().asBareJid() + ": inviting " + contact + " to " + conversation.getJid().asBareJid()); - MessagePacket packet = mMessageGenerator.invite(conversation, contact); + final MucOptions.User user = conversation.getMucOptions().findUserByRealJid(contact.asBareJid()); + if (user == null || user.getAffiliation() == MucOptions.Affiliation.OUTCAST) { + changeAffiliationInConference(conversation, contact, MucOptions.Affiliation.NONE, null); + } + final MessagePacket packet = mMessageGenerator.invite(conversation, contact); sendMessagePacket(conversation.getAccount(), packet); } From 0495470ca8412cea7eb55e72b199d3aa10523058 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 24 Aug 2021 15:32:10 +0200 Subject: [PATCH 55/82] pulled translations from transifex --- src/conversations/res/values-ar/strings.xml | 16 +- src/conversations/res/values-el/strings.xml | 2 +- src/conversations/res/values-ja/strings.xml | 14 +- src/conversations/res/values-sk/strings.xml | 14 + src/conversations/res/values-sv/strings.xml | 5 +- src/main/res/values-ar/strings.xml | 16 +- src/main/res/values-bn-rIN/strings.xml | 21 + src/main/res/values-cs/strings.xml | 13 + src/main/res/values-da-rDK/strings.xml | 3 + src/main/res/values-de/strings.xml | 6 +- src/main/res/values-el/strings.xml | 53 +-- src/main/res/values-es/strings.xml | 9 + src/main/res/values-fr/strings.xml | 5 + src/main/res/values-gl/strings.xml | 24 +- src/main/res/values-it/strings.xml | 3 + src/main/res/values-ja/strings.xml | 67 ++-- src/main/res/values-ml/strings.xml | 44 +++ src/main/res/values-pl/strings.xml | 5 +- src/main/res/values-pt-rBR/strings.xml | 2 +- src/main/res/values-ro-rRO/strings.xml | 11 +- src/main/res/values-ru/strings.xml | 3 + src/main/res/values-sk/strings.xml | 174 ++++++++ src/main/res/values-sr/strings.xml | 18 + src/main/res/values-sv/strings.xml | 27 +- src/main/res/values-tr-rTR/strings.xml | 4 + src/main/res/values-vi/strings.xml | 415 +++++++++++++++++++- src/main/res/values-zh-rCN/strings.xml | 2 +- src/quicksy/res/values-ar/strings.xml | 8 +- 28 files changed, 896 insertions(+), 88 deletions(-) create mode 100644 src/conversations/res/values-sk/strings.xml diff --git a/src/conversations/res/values-ar/strings.xml b/src/conversations/res/values-ar/strings.xml index 1e820a9cc..7d44818a7 100644 --- a/src/conversations/res/values-ar/strings.xml +++ b/src/conversations/res/values-ar/strings.xml @@ -3,4 +3,18 @@ اختر مزود خدمة XMPP الخاص بك استخدِم conversations.im أنشئ حسابًا جديدًا - \ No newline at end of file + هل تملك حساب XMPP؟؟ قد يكون ذلك ممكنا لو كنت تستعمل خدمة XMPP أخرى أو إستعملت تطبيق كونفرسايشنز سابقا. أو يمكنك صنع حساب XMPP جديد الآن. +ملاحظة: بعض خدمات البريد الإلكتروني تقدم حسابات XMPP. + XMPP هي خدمة مستقلة للتواصل بشبكة الرسائل المباشرة. يمكنك إستعمال هذه الخدمة مع أي خادم XMPP تختاره. +سعيا لراحتك جعلنا خلق حساب في كونفيرسايشنز سهلا مع مقدم خدمة خاص بالإستعمال مع كونفيرسايشنز. + لقد تمت دعوتك لـ %1$s. سيتم دلّك على طريقة صنع حساب. +عندما تختار %1$sكمقدّم خدمة سيصبح من الممكن لك التواصل مع مستعملين من أي خادم آخر عن طريق إعطائهم عنوانك الكامل على XMPP. + تمّت دعوتك إلى %1$s. تم إختيار إسم مستخدم خاص بك. سيتم قيادتك عبر طريقة صنع حساب. +سيمكنك التواصل مع مستخدمين من مزودين آخرين عبر إعطائهم كامل عنوانك XMPP. + سيرفر دعوتك + لم يتم التقاط الكود بطريقة جيّدة + إضغط على زر مشاركة لترسل إلى المتصل بك دعوة إلى %1$s. + إذا كان المتصل بك قريبا منك، يمكنه فحص الكود بالأسفل ليقبل دعوتك. + إنظم %1$s وتحدّث معي: %2$s + شارك إستدعاء مع... + \ No newline at end of file diff --git a/src/conversations/res/values-el/strings.xml b/src/conversations/res/values-el/strings.xml index 9ee6d96ae..7c87e66a3 100644 --- a/src/conversations/res/values-el/strings.xml +++ b/src/conversations/res/values-el/strings.xml @@ -10,7 +10,7 @@ Η πρόσκλησή σας στον διακομιστή Λάθος μορφοποίηση κώδικα παροχής Πατήστε το πλήκτρο διαμοιρασμού για να στείλετε στην επαφή σας μια πρόσκληση στο %1$s. - Αν η επαφή σας βρίσκεται κοντά σας, μπορεί επίσης να σκανάρει τον κωδικό παρακάτω για να αποδεχτεί την πρόσκλησή σας. + Αν η επαφή σας βρίσκεται κοντά σας, μπορεί επίσης να σαρώσει τον κωδικό παρακάτω για να αποδεχτεί την πρόσκλησή σας. Μπείτε στο %1$s και συνομιλήστε μαζί μου: %2$s Διαμοιρασμός πρόσκλησης με... \ No newline at end of file diff --git a/src/conversations/res/values-ja/strings.xml b/src/conversations/res/values-ja/strings.xml index 2a9af807d..0ab18b6de 100644 --- a/src/conversations/res/values-ja/strings.xml +++ b/src/conversations/res/values-ja/strings.xml @@ -1,12 +1,12 @@ - XMPPプロバイダーを選択してください - conversations.imを利用する - アカウントを作成 - XMPPアカウントをお持ちですか?既にほかのXMPPクライアントを利用しているか、Conversationsを利用したことがある場合はこちら。初めての方は、今すぐ新しいXMPPアカウントを作成できます。\nヒント: eメールのプロバイダーがXMPPアカウントも提供している場合があります。 - XMPPは、プロバイダーに依存しないインスタントメッセージのプロトコルです。XMPPサーバーならどこでも、このクライアントを使用することができます。\nよろしければ、Conversationsに最適化されたプロバイダーconversations.im¹で簡単にアカウントを作成することもできます。 - %1$sへ招待されました。アカウント作成手順をご案内します。 \n%1$sをプロバイダーに選択してほかのプロバイダーのユーザーと会話するには、XMPPのフルアドレスを相手にお知らせください。 - %1$sへ招待されました。ユーザーネームは既に選択されています。アカウント作成手順をご案内します。 \nほかのプロバイダーのユーザーと会話するには、XMPPのフルアドレスを相手にお知らせください。 + XMPP プロバイダーを選択してください + conversations.im を利用する + 新しいアカウントを作成 + XMPP アカウントをお持ちですか?既にほかの XMPP クライアントを利用しているか、 Conversations を利用したことがある場合はこちら。初めての方は、今すぐ新しい XMPP アカウントを作成できます。\nヒント: e メールのプロバイダーが XMPP アカウントも提供している場合があります。 + XMPP は、プロバイダーに依存しないインスタントメッセージのプロトコルです。 XMPP サーバーならどこでも、このクライアントを使用することができます。\nよろしければ、 Conversations に最適化されたプロバイダー conversations.im¹ で簡単にアカウントを作成することもできます。 + %1$s へ招待されました。アカウント作成手順をご案内します。 \n%1$s をプロバイダーに選択してほかのプロバイダーのユーザーと会話するには、 XMPP のフルアドレスを相手にお知らせください。 + %1$s へ招待されました。ユーザー名は既に選択されています。アカウント作成手順をご案内します。 \nほかのプロバイダーのユーザーと会話するには、 XMPP のフルアドレスを相手にお知らせください。 サーバーの招待 仮コードの書式が不正です 共有ボタンを叩いて、連絡先の %1$s に招待を送信する。 diff --git a/src/conversations/res/values-sk/strings.xml b/src/conversations/res/values-sk/strings.xml new file mode 100644 index 000000000..4897be11a --- /dev/null +++ b/src/conversations/res/values-sk/strings.xml @@ -0,0 +1,14 @@ + + + Vyberte si svojho XMPP poskytovateľa + Použiť conversations.im + Vytvoriť nové konto + Máte už svoje XMPP konto? Môže to tak byť v prípade, že už používate iného klienta XMPP alebo ste predtým používali Conversations. Ak nie, môžete si vytvoriť nové XMPP konto práve teraz.\nHint: Niektorí poskytovatelia emailu zároveň poskytujú aj XMPP kontá. + XMPP je sieť pre okamžité správy nezávislá od poskytovateľa. Tohto klienta môžete používať s akýmkoľvek XMPP serverom, ktorý si vyberiete..\nAvšak pre vaše pohodlie sme zjednodušili vytvorenie konta na conversations.im¹; poskytovateľ špeciálne vhodný na používanie s Conversations. + Boli ste pozvaný do %1$s. Prevedieme vás procesom vytvorenia konta..\nPo výbere %1$s ako poskytovateľa, budete môcť komunikovať s užívateľmi iných poskytovateľov tak, že im dáte vašu úplnú XMPP adresu. + Boli ste pozvaný do %1$s . Užívateľské meno vám už bolo vopred vybrané. Prevedieme vás procesom vytvorenia konta..\nBudete môcť komunikovať s užívateľmi iných poskytovateľov tak, že im dáte vašu úplnú XMPP adresu. + Ťuknite na tlačidlo zdieľať na odoslanie pozvánky do %1$s vášmu kontaktu. + Ak je váš kontakt blízko, na prijatie vašej pozvánky si môže nasnímať kód nižšie. + Pripojte sa k %1$sa rozprávajte sa so mnou: %2$s + Zdieľať pozvánku s... + \ No newline at end of file diff --git a/src/conversations/res/values-sv/strings.xml b/src/conversations/res/values-sv/strings.xml index 9212ad109..e898b5643 100644 --- a/src/conversations/res/values-sv/strings.xml +++ b/src/conversations/res/values-sv/strings.xml @@ -1,5 +1,8 @@ + Välj din XMPP leverantör Använd conversations.im Skapa nytt konto - \ No newline at end of file + Din server inbjudan + Dela inbjudan med... + \ No newline at end of file diff --git a/src/main/res/values-ar/strings.xml b/src/main/res/values-ar/strings.xml index 4dd5d5248..197b23a26 100644 --- a/src/main/res/values-ar/strings.xml +++ b/src/main/res/values-ar/strings.xml @@ -4,6 +4,7 @@ محادثة جديدة إدارة الحسابات إدارة الحساب + أغلق المحادثة بيانات جهة الإتصال تفاصيل مجموعة المحادثة تفاصيل القناة @@ -27,7 +28,7 @@ قائمة المحجوبين الآن منذ 1 دقيقة - دقائق %d منذ + منذ %d دقائق ارسال حل شيفرة الرسالة. الرجاء الإنتظار ... رسالة مشفرة عبر OpenPGP @@ -38,12 +39,14 @@ مشرف مشترك زائر + هل تريد حذف %sمن قائمة إتصالك؟ المحادثات مع هذا الشخص لن تحذف. هل ترغب في حجب %s من ارسال الرسائل لك? هل ترغب في انهاء حجب %s والسماح له بمراسلتك? هل تريد حجب جميع جهات الإتصال من %s? الغاء حجب جميع جهات الإتصال من %s? جهة الاتصال محجوبه محجوب + هل تريد حذف %sمن قائمة المفضلة؟ المحادثات مع هذا المفضل لن تحذف. تسجيل حساب جديد في سيرفر تغيير كلمة المرور في سيرفر مشاركة مع @@ -61,11 +64,16 @@ الغاء حجب حفظ موافق + %1$sتعطّل ارسال الآن لا تسألني ثانية + لا يمكن الإتصال بالحساب + لايمكن الإتصال بحسابات متعددة ارفاق ملف اضافة جهة اتصال فشل التسليم + الإستعداد لإرسال الصورة + الإستعداد لإرسال الصور جاري إرسال الملفات. الرجاء الإنتظار ... حذف سجل المحفوظات حذف سجل المحفوظات للمحادثة @@ -77,6 +85,7 @@ إرسال رسالة مشفرة عبر OMEMO إبعث رسالة مشفَّرة بـ أومي مو OMEMO إرسال رسالة مشفرة عبر OpenPGP + إسم مستخدم جديد تحت الإستعمال إرسال بدون تشفير فشل فك التشفير. ربما ليس لديك المفتاح الخاص الصحيح. OpenKeychain @@ -96,6 +105,7 @@ إهتز عند وصول رسالة جديدة إشعار ضوئي التنبيه الصوتي + تنبيه صوتي فترة السماح متقدم لا ترسل تقارير أخطاء @@ -112,6 +122,7 @@ اختيار صورة التقاط صورة الملف الذي حددته ليس صورة + لا يمكن تحويل ملف الصورة الملف غير موجود غير معروف معطلٌ موقتاً @@ -145,6 +156,7 @@ احجب عنوان XMPP username@example.com كلمة السر + الذاكرة مليئة، صورة كبيرة جدا هل تود إضافة %s إلى سجل عناوينك ؟ معلومات عن المضيف XEP-0313: إدارة أرشيف الرسائل @@ -159,6 +171,7 @@ متاح غير متاح آخر ظهور الآن + آخر مشاهدة منذ دقيقة آخر ظهور منذ %d دقيقة آخر ظهور منذ %d ساعة آخر ظهور منذ %d يوم @@ -642,4 +655,5 @@ افتح النسخة الاحتياطية يرجى إدخال الكلمة السرية للحساب مشغول + خيارات أخرى diff --git a/src/main/res/values-bn-rIN/strings.xml b/src/main/res/values-bn-rIN/strings.xml index cd8dbf187..bd426b737 100644 --- a/src/main/res/values-bn-rIN/strings.xml +++ b/src/main/res/values-bn-rIN/strings.xml @@ -46,6 +46,9 @@ নির্ধারক অংশগ্রহণকারী অতিথি + আপনি কি আপনার পরিচিতি তালিকা থেকে %s-কে অপসারণ করতে চান? এই যোগাযোগের সাথে কথোপকথনগুলি সরানো হবে না। + %s-কে বার্তা পাঠানো থেকে ব্লক করতে চান? + আপনি কি %s-কে আনব্লক করতে চান এবং তাদের আপনাকে বার্তা পাঠানোর অনুমতি দিতে চান? ব্যক্তিটিকে ব্লক্ করা হয়েছে ব্লক্ করা আছে সার্ভারে একটি নতুন অ্যকাউন্ট খোলা যাক @@ -90,8 +93,26 @@ OMEMO সাঙ্কেতিক বার্তা পাঠানো হোক v\\OMEMO সাঙ্কেতিক বার্তা পাঠানো হোক OpenPGP সাঙ্কেতিক বার্তা পাঠানো হোক + নতুন নাম ব্যবহার করা হচ্ছে + এনক্রিপ্ট না করেই পাঠানো হোক + ডিক্রিপ্ট করা যায়নি। হয়তো আপনার কাছে সঠিক Private Key নেই। + রিস্টার্ট্ + ইনস্টল্ + OpenKeychain ইনস্টল্ করতে হবে + প্রস্তাব দেওয়া হচ্ছে... + অপেক্ষা করা হচ্ছে... + কোনো OpenPGP Key খুঁজে পাওয়া যায়নি + বুকমার্ক করা যেগুলি + খোঁজা যাক এই ব্যক্তিকে ব্লক্ করা যাক ব্লকটা সরিয়ে ফেলা যাক পরিচিত ব্যক্তি না, থাক। + পরিচিত ব্যক্তিদের মধ্যে খোঁজা যাক + বার্তাগুলির মধ্যে খোঁজা যাক + সরাসরিভাবেই খোঁজা যাক + পাবলিক চ্যানেলে যোগ দেওয়া যাক + ব্যক্তিগত গ্রুপ চ্যাট তৈরি করুন + পাবলিক চ্যানেল তৈরি করা যাক + বর্তমান চ্যানেলগুলির মধ্যে থেকে খোঁজা যাক diff --git a/src/main/res/values-cs/strings.xml b/src/main/res/values-cs/strings.xml index 684632474..d0ddfa604 100644 --- a/src/main/res/values-cs/strings.xml +++ b/src/main/res/values-cs/strings.xml @@ -156,6 +156,7 @@ Soubor nenalezen Obecná I/O chyba. Že by již nebylo volné místo? Aplikace, kterou jste použil(a) k výběru obrázku, neposkytla dostatečná oprávnění ke čtení souboru.\n\nPoužijte jiného správce souborů k výběru obrázku. + Aplikace kterou jste použili pro nasdílení tohoto souboru nemá dostatečná oprávnění. Neznámý Dočasně vypnuto Online @@ -170,6 +171,7 @@ Registrace není podporována serverem Chybný registrační token Vyjednávání TLS selhalo + Doménu nelze ověřit Porušení podmínek Nekompatibilní server Chyba přenosu @@ -406,6 +408,7 @@ Nebylo možné změnit nastavení skupinového chatu Nikdy Než opět změním + Posunout Odpovědět Označit jako přečtené Vstup @@ -434,6 +437,8 @@ Nebyla nalezena aplikace pro zobrazení pozice Pozice Conversation zavřena + Opustil(a) soukromý skupinový chat + Opustil(a) veřejný kanál Nedůvěřovat systémovým CA Všechny certifikáty musí být schváleny ručně Odstranit certifikáty @@ -466,6 +471,7 @@ Stažení selhalo: Nelze zapsat soubor Tor síť není dostupná Bind chyba + Server není zodpovědný za tuto doménu Rozbité Dostupnost Pryč při uzamčení zařízení @@ -495,6 +501,7 @@ Vedení všech připojení po Tor síti vyžaduje aplikaci Orbot Hostname Port + Server nebo .onion adresa Toto není platné číslo portu Toto není platné hostname %1$d z %2$d účtů připojeno @@ -533,6 +540,7 @@ Odeslat opravenou zprávu Tento osobní otisk byl již bezpečně ověřen. Ťuknutím na \"Hotovo\" pouze potvrzujete, že %s je členem tohoto skupinového chatu. Tento účet byl vypnut + Bezpečnostní chyba: Neplatný přístup k souboru Nebyla nalezena aplikace umožňující sdílení URI Sdílet URI s…
Po zadání Vašeho telefonního čísla Vám Quicksy automaticky—na základě čísel ve Vašem telefonním seznamu—navrhne možné kontakty.

Přihlášením se do služby potvrzujete souhlas s našimi zásadami pro ochranu osobních údajů.]]>
@@ -744,6 +752,7 @@ Použít Plugin pro sdílení pozice namísto interní mapy Kopírovat webovou adresu Kopírovat XMPP adresu + HTTP sdílení souborů pro S3 Přímé vyhledávání Na úvodní obrazovce otevřít klávesnici a umístit kurzor do vyhledávacího pole Avatar skupinového chatu @@ -883,6 +892,7 @@ Toto vypadá jako adresa kanálu Sdílet soubory zálohy Záloha Conversations + Událost Otevřít zálohu Soubor, který jste zvolili, není soubor zálohy Conversations Tento účet byl již nastaven @@ -932,6 +942,7 @@ Nebylo možné přepnout kameru Připnout nahoru Odepnout shora + GPX trasa Nebylo možné opravit zprávu Všechny konverzace Tato konverzace @@ -961,7 +972,9 @@ Více možností Nenalezena žádná aplikace Pozvat do Conversations + Nelze načíst pozvánku Server nepodporuje vytváření pozvánek Žádný z aktivních účtů tuto funkci nepodporuje Zálohování zahájeno. Budete upozorněni, jakmile bude záloha hotova. + Nelze povolit video. diff --git a/src/main/res/values-da-rDK/strings.xml b/src/main/res/values-da-rDK/strings.xml index 9129da103..bbcd082f0 100644 --- a/src/main/res/values-da-rDK/strings.xml +++ b/src/main/res/values-da-rDK/strings.xml @@ -150,6 +150,7 @@ Fil ikke fundet General I/O fejl. Måske er du kørt tør for lagerplads? Appen du brugte til at vælge dette billede havde ikke tilstrækkelig tilladelse til at læse filen.\n\nBrug en anden filmanager til at vælge et billede. + Appen du brugte til at dele denne fil har ikke givet nok tilladelser. Ukendt Midlertidigt deaktiveret Online @@ -164,6 +165,7 @@ Registrering er ikke understøttet af server Ugyldig registreringstoken TLS forhandling mislykkedes + Domæne kan ikke verificeres Brud på retningslinjer Inkompatibel server Strømfejl @@ -960,4 +962,5 @@ Server understøtter ikke generering af invitationer Ingen aktive konti understøtter denne funktion Sikkerhedskopieringen er startet. Du får en notifikation, når den er afsluttet. + Kunne ikke aktivere video. diff --git a/src/main/res/values-de/strings.xml b/src/main/res/values-de/strings.xml index fe9d6d642..782b5825f 100644 --- a/src/main/res/values-de/strings.xml +++ b/src/main/res/values-de/strings.xml @@ -76,7 +76,7 @@ Jetzt abschicken Nie mehr nachfragen Verbindung zum Konto konnte nicht hergestellt werden - Verbindung zu mehreren Konto konnte nicht hergestellt werden + Verbindung zu mehreren Konten konnte nicht hergestellt werden Antippen, um deine Konten zu verwalten Datei auswählen Diesen fehlenden Kontakt zu deiner Kontaktliste hinzufügen? @@ -371,7 +371,7 @@ Aktuelles Passwort Neues Passwort Passwort kann nicht leer sein - Alle Konten aktiveren + Alle Konten aktivieren Alle Konten abschalten Aktion durchführen mit Keine Zugehörigkeit @@ -963,4 +963,4 @@ Keine aktiven Konten unterstützen diese Funktion Das Backup wurde gestartet. Du bekommst eine Benachrichtigung sobald es fertig ist. Video kann nicht aktiviert werden. - + diff --git a/src/main/res/values-el/strings.xml b/src/main/res/values-el/strings.xml index 57527772c..16a2a8742 100644 --- a/src/main/res/values-el/strings.xml +++ b/src/main/res/values-el/strings.xml @@ -53,7 +53,7 @@ Άρση αποκλεισμού όλων των επαφών από το %s; Η επαφή αποκλείστηκε Αποκλεισμένος - Θέλετε να αφαιρέσετε το %s ως σελιδοδείκτη; Οι συζητήσεις που σχετίζονται με αυτόν τον σελιδοδείκτη δεν θα αφαιρεθούν. + Θέλετε να αφαιρέσετε το %s από σελιδοδείκτη; Οι συζητήσεις που σχετίζονται με αυτόν τον σελιδοδείκτη δεν θα αφαιρεθούν. Εγγραφή νέου λογαριασμού στον διακομιστή Αλλαγή συνθηματικού στον διακομιστή Διαμοιρασμός με... @@ -89,7 +89,7 @@ Καθαρισμός ιστορικού Συζήτησης Θέλετε να διαγράψετε όλα τα μηνύματα αυτής της συζήτησης;\n\nΠροσοχή: Αυτή η ενέργεια δεν θα επηρεάσει μηνύματα που είναι αποθηκευμένα σε άλλες συσκευές ή εξυπηρετητές. Διαγραφή αρχείου - Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτό το αρχείο;\n\nΠροσοχή Αυτή η ενέργεια δεν θα διαγράψει αντίγραφα αυτού του αρχείου που είναι αποθηκευμένα σε άλλες συσκευές ή εξυπηρετητές. + Είστε βέβαιοι ότι θέλετε να διαγράψετε αυτό το αρχείο;\n\nΠροσοχή: Αυτή η ενέργεια δεν θα διαγράψει αντίγραφα αυτού του αρχείου που είναι αποθηκευμένα σε άλλες συσκευές ή εξυπηρετητές. Κλείσιμο της συζήτησης αμέσως μετά Επιλογή συσκευής Αποστολή μη κρυπτογραφημένου μηνύματος @@ -114,7 +114,7 @@ Δεν ήταν δυνατή η κρυπτογράφηση του μηνύματός σας γιατί οι επαφές σας δεν ανακοινώνουν το δημόσιο κλειδί τους.\n\nΠαρακαλώ ζητήστε από τις επαφές σας να εγκαταστήσουν το OpenPGP. Γενικά Αποδοχή αρχείων - Αυτόματη αποδοχή αρχείων μικρότερα από... + Αυτόματη αποδοχή αρχείων μικρότερων από... Συνημμένα Ειδοποίηση Δόνηση @@ -124,6 +124,7 @@ Κουδούνισμα Ήχος ειδοποίησης Ήχος ειδοποίησης για νέα μηνύματα + Ήχος κουδουνίσματος για εισερχόμενες κλήσεις Περίοδος Χάριτος Ο χρόνος σίγασης ειδοποιήσεων αφότου ανιχνευθεί δραστηριότητα σε μια από τις άλλες συσκευές σας. Για προχωρημένους @@ -133,7 +134,7 @@ Επιτρέψτε στις επαφές σας να γνωρίζουν όταν έχετε λάβει και διαβάσει τα μηνύματά τους Διεπαφή χρήστη Το OpenKeychain ανέφερε κάποιο σφάλμα. - Σφάλμα στο κλειδί κρυπτογράφησης + Σφάλμα στο κλειδί κρυπτογράφησης. Αποδοχή Έχει συμβεί κάποιο σφάλμα Σφάλμα @@ -149,6 +150,7 @@ Το αρχείο δεν βρέθηκε Γενικό σφάλμα εισόδου/εξόδου. Ίσως δεν έχετε ελεύθερο χώρο αποθήκευσης; Η εφαρμογή που χρησιμοποιήσατε για να επιλέξετε αυτή την εικόνα δεν παραχώρησε αρκετά δικαιώματα για την ανάγνωση του αρχείου.\n\nΧρησιμοποιήστε διαφορετικό διαχειριστή αρχείων για να επιλέξετε μια εικόνα + Η εφαρμογή που χρησιμοποιήσατε για να διαμοιραστείτε αυτό το αρχείο δεν παρείχε αρκετά δικαιώματα. Άγνωστο Προσωρινά απενεργοποιημένο Σε σύνδεση @@ -163,6 +165,7 @@ Ο διακομιστής δεν υποστηρίζει εγγραφή Άκυρο κουπόνι εγγραφής Αποτυχία διαπραγμάτευσης TLS + Ο τομέας δεν είναι επαληθεύσιμος Παραβίαση κανονισμού Μη συμβατός διακομιστής Σφάλμα μετάδοσης @@ -204,11 +207,11 @@ μη διαθέσιμος Ελλειπείς ανακοινώσεις δημοσίων κλειδιών συνδέθηκε τελευταία φορά μόλις τώρα - τελευταία σύνδεση πριν από 1 λεπτό + τελευταία σύνδεση πριν από ένα λεπτό τελευταία σύνδεση πριν από %d λεπτά - τελευταία σύνδεση πριν από 1 ώρα + τελευταία σύνδεση πριν από μία ώρα τελευταία σύνδεση πριν από %d ώρες - τελευταία σύνδεση πριν από 1 μέρα + τελευταία σύνδεση πριν από μία μέρα τελευταία σύνδεση πριν από %d μέρες Κρυπτογραφημένο μήνυμα. Παρακαλώ εγκαταστήστε το OpenKeychain για αποκρυπτογράφηση. Βρέθηκαν νέα μηνύματα κρυπτογραφημένα με OpenPGP @@ -217,7 +220,7 @@ v\\Αποτύπωμα OMEMO Αποτύπωμα OMEMO (πηγή μηνύματος) v\\Αποτύπωμα OMEMO (πηγή μηνύματος) - \'Αλλες συσκευές + Άλλες συσκευές Επαλήθευση των αποτυπωμάτων OMEMO Μεταφόρτωση κλειδιών... Έγινε @@ -327,7 +330,7 @@ Δημιουργία αντιγράφων ασφαλείας Το αντίγραφο ασφαλείας σας έχει δημιουργηθεί Τα αρχεία του αντιγράφου ασφαλείας έχουν αποθηκευτεί στο %s - Επαναφορά αντιγράφου ασφαλείας + Γίνεται επαναφορά αντιγράφου ασφαλείας Έχει γίνει επαναφορά του αντιγράφου ασφαλείας σας Μην παραλείψετε να ενεργοποιήσετε τον λογαριασμό. Επιλογή αρχείου @@ -338,7 +341,7 @@ Άνοιγμα του %s αποστολή (ολοκλήρωση %1$d%%) Προετοιμασία του αρχείου για διαμοιρασμό - %s προσφέρθηκε για μεταφόρτωση + Το %s προσφέρθηκε για μεταφόρτωση Ακύρωση μετάδοσης ο διαμοιρασμός του αρχείου απέτυχε η μεταφορά αρχείου ακυρώθηκε @@ -418,10 +421,10 @@ Αποστολή του %s Προσφορά του %s Απόκρυψη των εκτός σύνδεσης - Η επαφή %s πληκτρολογεί... - Η επαφή %s σταμάτησε να πληκτρολογεί - Οι επαφές %s πληκτρολογούν... - Η επαφές %s σταμάτησαν να πληκτρολογούν + Ο/Η %s πληκτρολογεί... + Ο/Η %s σταμάτησε να πληκτρολογεί + Οι %s πληκτρολογούν... + Οι %s σταμάτησαν να πληκτρολογούν Ειδοποιήσεις πληκτρολόγησης Επιτρέψτε στις επαφές σας να γνωρίζουν πότε γράφετε μηνύματα προς αυτές Αποστολή τοποθεσίας @@ -451,7 +454,7 @@ Αναζήτηση επαφών Αναζήτηση σελιδοδεικτών Αποστολή ιδιωτικού μηνύματος - Η επαφή %1$s αποχώρησε από την ομαδική συζήτηση + Ο/Η %1$s αποχώρησε από την ομαδική συζήτηση Όνομα χρήστη Όνομα χρήστη Αυτό δεν είναι έγκυρο όνομα χρήστη @@ -464,6 +467,8 @@ Ο διακομιστής δεν είναι υπεύθυνος για αυτόν τον τομέα Χαλασμένος Διαθεσιμότητα + Εκτός χρήσης όταν η οθόνη είναι κλειδωμένη + Εμφάνιση παρουσίας ως εκτός χρήσης όταν η συσκευή κλειδώνεται Απασχολημένος/η όταν βρίσκεται σε σιωπηρή λειτουργία Σημειώνει την παρουσία σας ως Απασχολημένος/η όταν η συσκευή είναι σε κατάσταση σιωπής Χρήση της κατάστασης δόνησης ως σιωπηρή κατάσταση @@ -501,7 +506,7 @@ Φόρτωση περισσότερων μηνυμάτων Το αρχείο διαμοιράστηκε με την επαφή %s Η εικόνα διαμοιράστηκε με την επαφή %s - Η εικόνες διαμοιράστηκαν με την επαφή %s + Οι εικόνες διαμοιράστηκαν με την επαφή %s Το κείμενο διαμοιράστηκε με την επαφή %s Απόδοση δικαιώματος στο %1$s για πρόσβαση στον εξωτερικό αποθηκευτικό χώρο Απόδοση δικαιώματος στο %1$s για πρόσβαση στην φωτογραφική μηχανή @@ -577,7 +582,7 @@ Απόδοση δικαιώματος χρήσης Internet Εγώ Η επαφή ζητά συνδρομή σε υπηρεσία παρουσίας - Επιτρέπω + Να επιτραπεί Δεν υπάρχει δικαίωμα για πρόσβαση στο %s Δεν βρέθηκε ο απομακρυσμένος διακομιστής Λήξη χρόνου για τον απομακρυσμένο διακομιστή @@ -833,7 +838,7 @@ Μην χρησιμοποιείτε τη λειτουργία επαναφοράς αντιγράφων ασφαλείας για να κλωνοποιήσετε (ταυτόχρονη εκτέλεση) μια εγκατάσταση. Η επαναφορά αντιγράφου ασφαλείας προσφέρεται μόνο για μεταφορές ή σε περίπτωση που έχετε χάσει την αρχική συσκευή. Αδυναμία επαναφοράς αντιγράφου ασφαλείας. Αδυναμία αποκρυπτογράφησης του αντιγράφου ασφαλείας. Είναι ο κωδικός σωστός; - Δημιουργία & Επαναφορά αντιγράφων ασφαλείας + Δημιουργία & Επαναφορά Εισάγετε τη διεύθυνση XMPP Δημιουργία ομαδικής συζήτησης Είσοδος σε δημόσιο κανάλι @@ -893,8 +898,8 @@ Παρακαλώ ενεργοποιήστε έναν λογαριασμό Νέα κλήση Εισερχόμενη κλήση - Εισερχόμενη κλήση βίντεο - Σύνδεση + Εισερχόμενη βιντεοκλήση + Γίνεται σύνδεση Συνδέθηκε Αποδοχή κλήσης Τερματισμός κλήσης @@ -909,7 +914,7 @@ Αποτυχία εφαρμογής Τερματισμός κλήσης Κλήση σε εξέλιξη - Κλήση βίντεο σε εξέλιξη + Βιντεοκλήση σε εξέλιξη Απενεργοποίηστε το Tor για να κάνετε κλήσεις Εισερχόμενη κλήση Εισερχόμενη κλήση · %s @@ -918,7 +923,7 @@ Εξερχόμενη κλήση · %s Αναπάντηση κλήση Κλήση ήχου - Κλήση βίντεο + Βιντεοκλήση Βοήθεια Εναλλαγή στη συζήτηση Το μικρόφωνο δεν είναι διαθέσιμο @@ -949,11 +954,13 @@ Κάποιο μήνυμα δεν ήταν δυνατό να παραδοθεί Κάποια μηνύματα δεν ήταν δυνατό να παραδοθούν - Αποτυχημένες διανομές + Αποτυχημένες παραδόσεις Περισσότερες επιλογές Δεν βρέθηκε εφαρμογή Πρόσκληση στο Conversations Αδυναμία ανάγνωσης πρόσκλησης Ο διακομιστής δεν υποστηρίζει την δημιουργία προσκλήσεων Κανένας από τους ενεργούς λογαριασμούς δεν υποστηρίζει αυτό το χαρακτηριστικό + Το αντίγραφο ασφαλείας δημιουργείται. Θα λάβετε ειδοποίηση όταν ολοκληρωθεί. + Αδυναμία ενεργοποίησης βίντεο. diff --git a/src/main/res/values-es/strings.xml b/src/main/res/values-es/strings.xml index 5be2d0e90..1383e8b98 100644 --- a/src/main/res/values-es/strings.xml +++ b/src/main/res/values-es/strings.xml @@ -124,6 +124,7 @@ Tono de llamada Sonido de notificación Sonido de notificación para nuevos mensajes + Tono para las nuevas llamadas Periodo de gracia El periodo de tiempo en el que las notificaciones están silenciadas tras detectar actividad en otro de tus dispositivos. Avanzado @@ -149,6 +150,7 @@ Archivo no encontrado Error general. ¿Es posible que no tengas espacio en disco? La aplicación usaste para seleccionar esta imagen no proporcionó suficientes permisos para leer el archivo.\n\nUtiliza un explorador de archivos diferente para seleccionar la imagen + La aplicación que has utilizado para compartir este archivo no presentó permisos suficientes Desconocido Deshabilitado temporalmente Conectado @@ -163,6 +165,7 @@ El servidor no soporta registros Token de registro inválido Error de negociación TLS + Dominio no verificable Policy violation Servidor incompatible Error de flujo @@ -215,6 +218,8 @@ OpenPGP Key ID Huella digital OMEMO Huella digital v\\OMEMO + Huella digital OMEMO (origen del mensaje) + Huella digital v\\OMEMO (origen del mensaje) Otros dispositivos Huellas digitales OMEMO de confianza Buscando claves... @@ -462,6 +467,8 @@ El servidor no es responsable de este dominio Error Disponibilidad + Ausente cuando el dispositivo esté bloqueado + Mostrar como Ausente cuando el dispositivo esté bloqueado Ocupado en modo silencio Mostrar como Ocupado cuando el dispositivo esté en modo silencio Modo vibración como modo silencio @@ -954,4 +961,6 @@ No se ha podido leer la invitación El servidor no soporta la creación de invitaciones Ninguna cuenta activa soporta esta característica + La copia de seguridad ha empezado. Recibirás una notificación cuando se haya completado. + No se ha podido habilitar el vídeo. diff --git a/src/main/res/values-fr/strings.xml b/src/main/res/values-fr/strings.xml index cb8669df9..256baacab 100644 --- a/src/main/res/values-fr/strings.xml +++ b/src/main/res/values-fr/strings.xml @@ -124,6 +124,7 @@ Sonnerie Son des notifications Son de notification pour les nouveaux messages + Sonnerie d\'appel entrant Période sans notification La durée pendant laquelle les notifications sont désactivées après la détection d\'une activité sur l\'un de vos autres appareils. Avancé @@ -149,6 +150,7 @@ Impossible de trouver le fichier Erreur générale d\'E/S. Avez-vous encore de l\'espace libre ? L\'application utilisée ne donne pas la permission de lire l\'image.\n\nUtilisez une autre application pour choisir une image. + L\'app avec laquelle vous avez partagé ce fichier n\'a pas fourni assez de permissions. Inconnu Désactivé temporairement En ligne @@ -163,6 +165,7 @@ Inscription non supportée par le serveur Jeton d’inscription invalide La négociation TLS a échoué + Domaine non vérifiable Violation de politique Serveur incompatible Erreur de flux @@ -215,6 +218,7 @@ ID de clé OpenPGP Empreinte OMEMO v\\Empreinte OMEMO + Empreinte OMEMO (origine du message) Autres appareils Faire confiance aux empreintes OMEMO Récupération des clés… @@ -462,6 +466,7 @@ Le serveur n\'est pas responsable pour ce domaine Détraqué Disponibilité + Absent quand l\'appareil est verrouillé Occupé en mode silence Occupé lorsque l\'appareil est en mode silencieux Indisponible en mode vibreur diff --git a/src/main/res/values-gl/strings.xml b/src/main/res/values-gl/strings.xml index f7c151661..2baea5f16 100644 --- a/src/main/res/values-gl/strings.xml +++ b/src/main/res/values-gl/strings.xml @@ -85,12 +85,12 @@ Preparándose para enviar a imaxe Preparándose para enviar imaxes Compartindo ficheiros. Por favor agarde... - Limpar historial - Limpar historial de conversa + Baleirar historial + Eliminar historial da conversa ¿Queres eliminar as mensaxes desta conversa?\n\nAviso: Esto non lle afecta as mensaxes gardadas noutros dispositivos ou servidores. Eliminar ficheiro Está segura de querer eliminar este ficheiro?\n\nAviso: Esto non eliminará as copias de este ficheiro que están gardadas en outros dispositivos ou servidores. - Pechar esta conversa a posteriori + Pechar a conversa tras baleirar Escoller dispositivo Enviar mensaxe non cifrada Enviar mensaxe @@ -144,7 +144,7 @@ Solicitar actualizacións de presenza Seleccionar imaxe Facer foto - Por defecto otorgar peticiones de suscripción + Por defecto conceder solicitudes de subscrición O arquivo seleccionado non é unha imaxe Non se puido converter o ficheiro de imaxe Arquivo non atopado @@ -252,7 +252,7 @@ Saír Contacto engadido a túa lista de contactos Volver a engadir - %s leeu ate este punto + %s leu ata este punto %s leu ate este punto %1$s + %2$d outras leron ata este punto Todas leron ate este punto @@ -349,8 +349,8 @@ Non se atopou unha app para abrir o ficheiro Non se atopou app para abrir a ligazón Non se atopou app para ver o contacto - Etiquetas dinámicas - Mostrar etiquetas de só lectura baixo os contactos + Información do estado + Mostra o estado debaixo do nome do contacto Habilitar notificacións Non se atopou ningún servidor de conversa en grupo Non se puido crear a conversa en grupo @@ -614,11 +614,11 @@ Non confiables Código de barras 2D non válido Baleirar o cartafol da caché (utilizado pola cámara) - Limpar caché + Baleirar caché Baleirar almacenaxe privada Baleirar a almacenaxe privada onde se gardan os ficheiros (poderán volver a descargarse desde o servidor) Seguín esta ligazón desde unha fonte de confianza - Vai verificar as chaves OMEMO de %1$s despois de pulsar na ligazón. Esto só é seguro si sigueu esta ligazón desde unha fonte de confianza onde só %2$s a podería ter publicado. + Vas verificar as chaves OMEMO de %1$s despois de premer na ligazón. Esto só é seguro se seguiches esta ligazón desde unha fonte de confianza onde só %2$s a podería ter publicado. Validar chaves OMEMO Mostrar inactivos Amagar inactivos @@ -707,8 +707,8 @@ OMEMO utilizarase por defecto para as novas conversas. OMEMO terá que ser activado explícitamente para novas conversacións. Crear acceso directo - Tamaño da fonte - O tamaño de fonte relativo a esta app + Tamaño da letra + O tamaño relativo da letra que utiliza a app. Activado por defecto Desactivado por defecto Pequena @@ -963,4 +963,4 @@ Ningunha conta activa soporta esta función Comezou a creación da copia de apoio. Recibirás unha notificación cando remate. Non se puido activar o vídeo. - + diff --git a/src/main/res/values-it/strings.xml b/src/main/res/values-it/strings.xml index 5fc619622..9271b113d 100644 --- a/src/main/res/values-it/strings.xml +++ b/src/main/res/values-it/strings.xml @@ -150,6 +150,7 @@ File non trovato Errore di I/O generico. Forse hai esaurito lo spazio? L’app che hai usato per selezionare questa immagine non ha fornito autorizzazioni sufficienti per leggere il file.\n\nUsa un gestore di file differente per scegliere un’immagine + L\'app che hai usato per condividere questo file non ha fornito autorizzazioni sufficienti. Sconosciuto Disattivato temporaneamente Online @@ -164,6 +165,7 @@ Registrazione non supportata dal server Token di registrazione non valido Negoziazione TLS fallita + Dominio non verificabile Violazione della policy Server non compatibile Errore di stream @@ -960,4 +962,5 @@ Il server non supporta la generazione di inviti Nessun account attivo supporta questa funzione Il backup è iniziato. Riceverai una notifica una volta completato. + Impossibile attivare il video. diff --git a/src/main/res/values-ja/strings.xml b/src/main/res/values-ja/strings.xml index 83b981881..f1a3daa2f 100644 --- a/src/main/res/values-ja/strings.xml +++ b/src/main/res/values-ja/strings.xml @@ -27,8 +27,8 @@ アカウントで共有 ブロック一覧 ちょうど今 - 1 分前 - %d 分前 + 1分前 + %d分前 未読%d件 @@ -40,7 +40,7 @@ 正しくないニックネームです 管理者 所有者 - モデレーター + 調停者 参加者 訪問者 連絡先名簿から %s を削除しますか? この連絡先との会話は削除されません。 @@ -147,6 +147,7 @@ ファイルが見つかりません 一般的な I/O エラー。おそらく空き容量がなくなっていませんか? あなたが画像の選択のために使用したアプリは、読み取りに必要なアクセス権がありません。\n\n別のファイルマネージャを使用して、画像を選択してください。 + このファイルを共有するために使用したアプリに、十分な許可が与えられていませんでした。 不明 一時的に無効 オンライン @@ -161,6 +162,7 @@ サーバーが登録をサポートしていません トークンが無効です TLS ネゴシエーションに失敗しました + 検証不可能なドメイン ポリシー違反 互換性のないサーバー ストリーム エラー @@ -189,7 +191,7 @@ %s をお使いのアドレス帳に追加しますか? サーバー情報 XEP-0313: メッセージ アーカイブ管理 - XEP-0280: メッセージ カーボン + XEP-0280: メッセージ複写 XEP-0352: クライアント状態表示 XEP-0191: ブロッキング コマンド XEP-0237: 名簿バージョニング @@ -202,12 +204,12 @@ 利用不可 公開鍵の告知がありません ちょうど今会いました - 1 分前に会いました - %d 分前に会いました - 1 時間前に会いました - %d 時間前に会いました - 1 日前に会いました - %d 日前に会いました + 1分前に会いました + %d分前に会いました + 1時間前に会いました + %d時間前に会いました + 1日前に会いました + %d日前に会いました 暗号化されたメッセージです。復号するには OpenKeychain をインストールしてください。 新しい OpenPGP 暗号化されたメッセージが見つかりました OpenPGP 鍵 ID @@ -298,8 +300,8 @@ %s 上でホストされた HTTP ホストの %s を確認中 接続されていません。後でもう一度お試しください - %s サイズを確認 - %2$s で %1$s のサイズを確認 + %s の大きさを確認 + %2$s で %1$s の大きさを確認 メッセージオプション 引用 引用として貼り付け @@ -310,8 +312,8 @@ XMPP アドレスをクリップボードにコピーしました エラーメッセージをクリップボードにコピーしました ウェブアドレス - 2D バーコードをスキャン - 2D バーコードを表示 + 二次元バーコードをスキャン + 二次元バーコードを表示 ブロック一覧を表示 アカウントの詳細 確認 @@ -494,12 +496,12 @@ %d メッセージ - さらにメッセージをロード + さらにメッセージを読み込む %s でファイル共有 %s で画像共有 %s で画像共有 %s でテキスト共有 - %1$s に外部ストレージへのアクセス権を付与 + %1$s に外部ストレージへのアクセス権を付与してください %1$s にカメラへのアクセス権を付与 連絡先と同期 %1$s はあなたのアドレス帳にアクセスして、あなたのXMPP 連絡先名簿と照合する権限を求めています。\nこれにより、連絡先のフルネームとアバターが表示されます。\n\n%1$s は、あなたのサーバーに何かをアップロードすることなく、あなたのアドレス帳を読み込んでローカルに照合するだけです。 @@ -528,7 +530,7 @@ …で URI を共有
電話番号を入力して登録すると、アドレス帳に登録されている電話番号をもとに、Quicksyが自動的に連絡先を提案します。

登録すると、我々のプライバシーポリシーに同意することになります。]]>
同意して続行 - conversations.im のアカウント作成のための指南が設定されています。¹\nconversations.im をプロバイダーとして選択した場合、あなたの完全なXMPPアドレスを与えることで、他のプロバイダーのユーザーと連絡をとることができます。 + conversations.im 上にアカウントを作成する設定の指南です。¹\nconversations.im をプロバイダーとして選択した場合、あなたの完全な XMPP アドレスを他のプロバイダーのユーザーに示すことで、その人と連絡をとることができます。 あなたの完全なXMPPアドレスは: %s アカウントを作成 独自のプロバイダーを使用する @@ -538,8 +540,8 @@ ステータスメッセージ いつでもチャットできます オンライン - 離席中 - 利用不可 + 離席 + 不在 取込中 安全なパスワードが生成されました お使いのデバイスは電池最適化の停止をサポートしていません @@ -553,13 +555,13 @@ ブロードキャストを使用 - Conversations を使用するとき、連絡先に知らせましょう + Conversations を使用するときに、連絡先に知らせましょう プライバシー テーマ カラーパレットの選択 自動 - ライト - ダーク + + 緑の背景 受信したメッセージに緑の背景を使用します OpenKeychain に接続できません @@ -603,11 +605,11 @@ 認証されていない連絡先からの新規デバイスを信頼するが、認証されている連絡先からの新規デバイスについては手動での確認を求める。 OMEMO 鍵を盲目的に信用していた。つまり、他の人かもしれないし、誰かが盗聴しているかもしれない。 信頼されていない - 不正な 2D バーコード - キャッシュフォルダーをクリアします (カメラアプリで使用) - キャッシュをクリア - プライベートストレージをクリア - ファイルが保存されているプライベートストレージをクリアします (サーバーから再ダウンロードできます) + 不正な二次元バーコード + キャッシュフォルダを消去します (カメラアプリで使用) + キャッシュを消去 + プライベートストレージを消去 + ファイルが保存されているプライベートストレージを消去します (サーバーから再ダウンロードできます) 信頼できるソースからこのリンクをたどりました リンクをクリックした後、%1$s の OMEMO 鍵を検証しようとしています。 これは、%2$s がこのリンクを公開した、信頼できるソースからこのリンクをたどった場合にのみ安全です。 OMEMO 鍵を検証 @@ -693,7 +695,7 @@ 新しい会話をするためには、OMEMOを明示的にオンにする必要があります。 ショートカットを作成 フォントの大きさ - このアプリで使用される相対フォントサイズ + このアプリで使用される相対的なフォントの大きさ デフォルトでオン デフォルトでオフ @@ -849,11 +851,11 @@ 参加者を検索 ファイルが大きすぎます 添付 - 談話室発見 + 談話室を発見 談話室を検索 プライバシー侵害の可能性あり! - search.jabber.networkを利用します。

この機能を使うと、あなたののIPアドレスや検索キーワードがそのサービスに送信されます。詳しくは、プライバシーポリシーをご覧ください。]]>
- 私は既にアカウントを持っています + search.jabber.networkを利用します。

この機能を使うと、あなたののIPアドレスや検索キーワードがそのサービスに送信されます。詳しくは、プライバシーポリシーをご覧ください。]]>
+ 既にアカウントを持っています 存在するアカウントを追加 新しいアカウントを登録 これはドメインアドレスのようです @@ -873,7 +875,7 @@ jabber.network ローカルサーバー ほとんどのユーザーは、公開されている XMPP エコシステム全体からより良い提案を得るために、‘jabber.network’を選択するはずです。 - 談話室発見方法 + 談話室の発見方法 アカウントを有効にしてください 通話をする 通話着信 @@ -937,4 +939,5 @@ サーバーは招待をサポートしていません この機能をサポートするアクティブなアカウントがありません バックアップを開始しました。 バックアップが完了すると通知が届きます。 + 映像を有効化できません。 diff --git a/src/main/res/values-ml/strings.xml b/src/main/res/values-ml/strings.xml index e4e82c214..7adca9d89 100644 --- a/src/main/res/values-ml/strings.xml +++ b/src/main/res/values-ml/strings.xml @@ -86,6 +86,7 @@ ഫയലുകൾ സ്വീകരിക്കൂ അറ്റാച്ചുമെന്റുകൾ അറിയിപ്പ് + വൈബ്രേറ്റ് ചെയ്യൂ LED അറിയിപ്പ് റിംഗ്‌ടോൺ അറിയിപ്പ് ശബ്‌ദം @@ -106,8 +107,11 @@ ഓൺലൈൻ ഓഫ്‌ലൈൻ സെർവർ കണ്ടെത്തിയില്ല + കണക്റ്റിവിറ്റി ഇല്ല + രജിസ്ട്രേഷൻ പരാജയപ്പെട്ടു ഉപയോക്തൃനാമം ഇതിനകം നിലവിലുണ്ട് രജിസ്ട്രേഷൻ പൂർത്തിയായി + നയ ലംഘനം സുരക്ഷിതമല്ലാത്ത OTR OpenPGP @@ -152,6 +156,7 @@ തിരഞ്ഞെടുക്കൂ കോൺ‌ടാക്റ്റ് ഇതിനകം നിലവിലുണ്ട് ചേരുക + അടയാളക്കുറിപ്പായി സംരക്ഷിക്കൂ ഗ്രൂപ്പ് ചാറ്റ് നശിപ്പിക്കൂ ചാനൽ നശിപ്പിക്കൂ വിഷയം @@ -166,6 +171,7 @@ അടുത്തത് സെഷൻ സ്ഥാപിച്ചു ഒഴിവാക്കൂ + പ്രാപ്തമാക്കൂ ഗ്രൂപ്പ് ചാറ്റിന് രഹസ്യവാക്ക് ആവശ്യമാണ് രഹസ്യവാക്ക് നൽകുക ഇപ്പോൾ അഭ്യർത്ഥിക്കുക @@ -175,6 +181,7 @@ %s-നെ കുറിച്ച് ആരംഭ സമയം മറ്റുള്ളവ + ഈ ഗ്രൂപ്പ് ചാറ്റിൽ നിന്ന് നിങ്ങളെ നിരോധിച്ചിരിക്കുന്നു നിങ്ങളെ ഗ്രൂപ്പ് ചാറ്റിൽ നിന്ന് പുറത്താക്കി %s അക്കൗണ്ട് ഉപയോഗിക്കുന്നു %s-ന്റെ വലുപ്പം പരിശോധിക്കൂ @@ -214,6 +221,7 @@ ചാനലിൽ നിന്ന് നിരോധിക്കൂ ഇപ്പോൾ നിരോധിക്കൂ സ്വകാര്യ, അംഗങ്ങൾ മാത്രം + നിങ്ങൾ പങ്കെടുക്കുന്നില്ല മറുപടി വായിച്ചതായി കാണിക്കൂ എന്റെർ കീ അയയ്ക്കും @@ -222,6 +230,10 @@ ചിത്രം %s അയയ്ക്കുന്നു %s ടൈപ്പുചെയ്യുന്നു… + %s ടൈപ്പുചെയ്യുന്നു… + ലൊക്കേഷൻ അയയ്‌ക്കുക + ലൊക്കേഷൻ കാണിക്കൂ + ലൊക്കേഷൻ റദ്ദാക്കൂ സമീപകാലത്ത് ഉപയോഗിച്ചത് കോൺ‌ടാക്റ്റുകൾ തിരയുക @@ -250,25 +262,40 @@ ഓൺലൈൻ ലഭ്യമല്ല തിരക്കിലാണ് + പങ്കെടുക്കുന്നവരെ തിരഞ്ഞെടുക്കുക + ഗ്രൂപ്പ് ചാറ്റ് സൃഷ്ടിക്കുന്നു… വീണ്ടും ക്ഷണിക്കൂ + സ്വകാര്യത + രൂപഭംഗി + കമ്പ്യൂട്ടർ + മൊബൈൽ ഫോൺ ഞാൻ അനുവദിക്കൂ + + %d മാസം + %d മാസം + മുഴുവൻ മേഖലയും തടയുക ഇപ്പോൾ സജീവം വെബ്സൈറ്റ് തുറക്കൂ ഇന്ന് ഇന്നലെ + ക്ലിപ്പ്ബോർഡിലേയ്ക്ക് പകർത്തുക സന്ദേശം ഒരിക്കൽ പങ്കിടുക സന്ദേശങ്ങൾ തിരയുക GIF + വിളിപ്പേര് പേര് + ഗ്രൂപ്പ് ചാറ്റിന്റെ പേര് സന്ദേശങ്ങൾ കോളുകൾ സന്ദേശങ്ങൾ നിശബ്‌ദ സന്ദേശങ്ങൾ പങ്കെടുക്കുന്നവർ + റദ്ദാക്കി + രാജ്യ കോഡ് തെറ്റാണ് ഒരു രാജ്യം തിരഞ്ഞെടുക്കൂ ഫോൺ നമ്പർ നിങ്ങളുടെ ഫോൺ നമ്പർ ഉറപ്പാക്കൂ @@ -283,8 +310,25 @@ നിങ്ങളുടെ പേര് നൽകുക അക്കൗണ്ട് തിരഞ്ഞെടുക്കൂ XMPP വിലാസം നൽകുക + ഗ്രൂപ്പ് ചാറ്റ് സൃഷ്ടിക്കുക + പൊതു ചാനലിൽ ചേരുക XMPP വിലാസം + ഫയൽ വളരെ വലുതാണ് + ചാനലുകൾ തിരയുക നിലവിലുള്ള അക്കൗണ്ട് ചേർക്കുക + ഈ പ്രവർത്തനം നടത്താൻ കഴിഞ്ഞില്ല + പൊതു ചാനലിൽ ചേരുക… + jabber.network + കോൾ സ്വീകരിക്കുന്നു + കോൾ അവസാനിപ്പിക്കുന്നു + ഇൻകമിംഗ് കോൾ സഹായം + നിങ്ങളുടെ മൈക്രോഫോൺ ലഭ്യമല്ല GPX ട്രാക്ക് + എല്ലാ സംഭാഷണങ്ങളും + ഈ സംഭാഷണം + നിങ്ങളുടെ അവതാർ + കൂടുതൽ ഓപ്ഷനുകൾ + Conversations-ലേക്ക് ക്ഷണിക്കുക + ബാക്കപ്പ് ആരംഭിച്ചു. അത് പൂർത്തിയായിക്കഴിഞ്ഞാൽ നിങ്ങൾക്ക് ഒരു അറിയിപ്പ് ലഭിക്കും. diff --git a/src/main/res/values-pl/strings.xml b/src/main/res/values-pl/strings.xml index 04bee8888..eef1ba92b 100644 --- a/src/main/res/values-pl/strings.xml +++ b/src/main/res/values-pl/strings.xml @@ -156,6 +156,7 @@ Nie odnaleziono pliku Ogólny błąd wejścia/wyjścia Aplikacja użyta do wyboru obrazu nie zezwoliła na odczyt pliku.\n\nWybierz obraz przy użyciu innego menedżera plików + Aplikacja której użyłeś do udostępnienia pliku nie dostarczyła odpowiednich uprawnień. Nieznany Tymczasowo wyłączono Połączono @@ -170,6 +171,7 @@ Ten serwer nie wspiera rejestracji Nieprawidłowy żeton rejestracji Nie powiodła się negocjacja TLS + Nie można zweryfikować tej domeny Naruszenie zasad Serwer niekompatybilny Błąd strumienia @@ -924,7 +926,7 @@ Administrator twojego serwera będzie mógł czytać twoje wiadomości, ale moż Połączony Akceptowanie połączenia Kończenie połączenia - Odbierz + Połącz Odrzuć Wyszukiwanie urządzeń Dzwonienie @@ -987,4 +989,5 @@ Administrator twojego serwera będzie mógł czytać twoje wiadomości, ale moż Serwer nie wspiera tworzenia zaproszeń Nie ma aktywnych kont wspierających tę funkcję Tworzenie kopii zapasowej się rozpoczęło. Dostaniesz powiadomienie kiedy się zakończy. + Nie można włączyć wideo. diff --git a/src/main/res/values-pt-rBR/strings.xml b/src/main/res/values-pt-rBR/strings.xml index f638735f8..ffc3c0682 100644 --- a/src/main/res/values-pt-rBR/strings.xml +++ b/src/main/res/values-pt-rBR/strings.xml @@ -963,4 +963,4 @@ Nenhuma conta ativa suporta esse recurso O backup foi iniciado. Você receberá uma notificação assim que ele for concluído. Não foi possível habilitar o vídeo. - + diff --git a/src/main/res/values-ro-rRO/strings.xml b/src/main/res/values-ro-rRO/strings.xml index abc6a99af..d3ddbe6ca 100644 --- a/src/main/res/values-ro-rRO/strings.xml +++ b/src/main/res/values-ro-rRO/strings.xml @@ -135,6 +135,8 @@ Trimițând date despre erori ajutați la continuarea dezvoltării aplicației Confirmă mesajele Contactele sunt notificate atunci când ați primit un mesaj și l-ați citit + Previne captura ecranului + Ascunde conținutul în managerul de aplicații și blochează captura de ecran Opțiuni interfață OpenKeychain a raportat o eroare. Cheie invalidă pentru criptare. @@ -153,6 +155,7 @@ Fișierul nu a fost găsit Eroare I/O generala. Poate ați rămas fără spațiu liber? Aplicația folosită pentru selecția acestei imagini nu a oferit destule permisiuni pentru a putea citii fișierul.\n\nFolosiți un alt manager de fișiere pentru a alege o imagine + Aplicația pe care ați folosit-o pentru a partaja acest fișier nu a furnizat suficiente permisiuni. Necunoscut Dezactivat temporar Conectat @@ -167,6 +170,7 @@ Serverul nu permite înregistrarea Simbol de înregistrare invalid Negociere TLS eşuată + Domeniul nu se poate verifica Încălcare condiții furnizare serviciu Server incompatibil Eroare de date @@ -415,6 +419,7 @@ audio video imagine + grafic vectorial document PDF Aplicație Android Contact @@ -921,6 +926,7 @@ Conexiune pierdută Apel anulat Eroare de aplicație + Problemă la verificare Închide Apel în curs Apel video în curs @@ -973,4 +979,7 @@ Serverul nu suportă generarea de invitații Nici un cont activ nu suporta această caracteristică Se creează copia de siguranță. Veți primi o notificare când acesta este completă. - + Nu s-a putut activa camera video. + Document text + + diff --git a/src/main/res/values-ru/strings.xml b/src/main/res/values-ru/strings.xml index d2b0c26e7..e1405feed 100644 --- a/src/main/res/values-ru/strings.xml +++ b/src/main/res/values-ru/strings.xml @@ -156,6 +156,7 @@ Файл не найден Общая ошибка ввода/вывода. Возможно, на устройстве недостаточно свободного места? У приложения, которым вы выбрали это изображение, недостаточно прав, чтобы прочитать этот файл.\n\nПожалуйста, используйте другой файловый менеджер, чтобы выбрать это изображение. + Приложение, которое вы использовали для публикации этого файла, не предоставило достаточно разрешений. Неизвестен Временно отключён В сети @@ -170,6 +171,7 @@ Сервер не поддерживает возможность регистрации Неправильный токен регистрации Не удалось согласовать TLS + Домен не поддается проверке Нарушение правил Несовместимый сервер Ошибка потока @@ -986,4 +988,5 @@ Сервер не поддерживает создание приглашений Ни один активный аккаунт не поддерживает эту функцию Резервное копирование было начато. Вы получите уведомление, как только оно будет завершено. + Невозможно включить видео. diff --git a/src/main/res/values-sk/strings.xml b/src/main/res/values-sk/strings.xml index 6fb10af6b..792420be4 100644 --- a/src/main/res/values-sk/strings.xml +++ b/src/main/res/values-sk/strings.xml @@ -3,38 +3,69 @@ Nastavenia Nová konverzácia Nastavenie účtov + Nastaviť účet + Zavrieť rozhovor Detaily kontaktu + Detaily skupinového rozhovoru + Detaily kanála Pridať účet Upraviť meno + Pridať do kontaktov Vymazať zo zoznamu Zablokovať kontakt Odblokovať kontakt Zablokovať doménu Odblokovať doménu + Zablokovať účastníka + Odblokovať účastníka Nastavenie účtov Nastavenia Zdieľať s konverzáciou Začať konverzáciu + Vybrať Kontakt + Vyberte Kontakty + Zdieľať cez účet Zablokovať zoznam práve teraz pred 1 minútou pred %d minútami + + %dneprečítaný rozhovor + + + %dneprečítaných rozhovorov + + + %dneprečítaných rozhovorov + + + %dneprečítaných rozhovorov + + posielam... Dešifrujem správu. Čakajte, prosím… + OpenPGP šifrovaná správa Prezývka už existuje + Chybná prezývka Administrátor Vlastník Moderátor Účastník Návštevník + Chcete vymazať %sz vašich kontaktov? Rozhovory s týmto kontaktom nebudú zmazané. Chceli by ste zablokovať prijímanie správ od %s? Chceli by ste odblokovať %s a povoliť prijímanie správ? Zablokovať všetky kontakty od %s? Odblokovať všetky kontakty od %s? Kontakt zablokovaný + Zablokovaný + Chcete vymazať %sako záložku? Rozhovory s touto záložkou nebudú zmazané. Registrovať nový účet na serveri Zmeniť heslo na serveri Zdieľať s + Začať rozhovor + Pozvať kontakt + Pozvať Kontakty Kontakt Zrušiť @@ -46,33 +77,70 @@ Odblokovať Uložiť OK + %1$ssa zrútila + Pomocou vášho XMPP konta nám pošlite záznam o zlyhaní, ktorý nám pomôže vo vývoji %1$s. Poslať teraz Nepýtať sa znova + Nedá sa pripojiť k účtu + Nedá sa pripojiť k viacerým kontám + Ťapnite na správu vášho účtu Priložiť súbor + Pridať tento chýbajúci kontakt do vašich kontaktov? Pridať kontakt doručenie zlyhalo + Pripravujem odoslanie obrázka + Pripravujem odoslanie obrázkov + Zdieľam súbory. Prosím čakajte... Vymazať históriu Vymazať históriu konverzácií + Chcete vymazať všetky správy v tomto rozhovore?\n\nUpozornenie:Nebude to mať vplyv na správy uložené na ostatných zariadeniach alebo serveroch. + Zmazať súbor + Ste si istý, že chcete tento súbor zmazať?\n\nUpozornenie:Nevymažú sa kópie súborov, ktoré sú uložené na ostatných zariadeniach alebo serveroch. + Potom zavrieť tento rozhovor + Zvoliť zariadenie Poslať nezašifrovanú správu + Poslať správu + Poslať správu na %s Poslať OMEMO šifrovanú správu + Poslať v\\OMEMO šifrovanú správu Poslať OpenPGP šifrovanú správu + Používa sa nová prezývka Poslať nešifrované Zašifrovanie zlyhalo. Možno nemáte správny privátny kľúč. OpenKeychain Reštartovať Inštalovať + Prosím, nainštalujte OpenKeychain ponúka… čakám… Nenašiel sa žiadny OpenPGP kľúč + Nepodarilo sa zašifrovať vašu správu, pretože váš kontakt nezverejňuje jeho verejný kľúč.\n\nPožiadajte prosím váš kontakt, aby si nastavil OpenPGP. Nenašli sa žiadne OpenPGP kľúče + Nemôžem zašifrovať Vašu správu, pretože Vaše kontakty neoznamujú ich verejné kľúče.\n\nPoproste ich, aby si nastavili OpenPGP. Všeobecné Prijať súbory Automaticky prijať súbory menšie ako… + Prílohy + Oznámenie Vibrovať + Vibrovať, keď príde nová správa + LED notifikácia + Blikať notifikačným svetlom, keď príde nová správa + Zvonenie + Zvuk oznámenia + Zvuk oznámenia nových správ + Zvonenie pre prichádzajúce hovory + Ochranná doba + Pokročilé Neodosielať detaily o zlyhaní aplikácie + Keď pošlete detaily o dôvode zlyhania, pomáhate vývoju Potvrdzovať správy + Dajte vedieť svojim kontaktom, keď prijmete a prečítate si správy + Prostredie + Nesprávny kľúč na šifrovanie. Prijať Došlo k chybe + Chyba Váš účet Zasielať zmeny stavu Prijímať zmeny stavu @@ -81,8 +149,10 @@ Odfotiť Aktívne povoliť vyžiadanie zmeny stavu Vybraný súbor nie je obrázok + Nemohol som konvertovať obrázkový súbor Súbor sa nenašiel Všeobecná I/O chyba. Možno už nie je voľné miesto? + Aplikácia, ktorú ste použili na zdieľanie tohto súboru neposkytla dostatočné povolenia. Neznámy Dočasne vypnutý Online @@ -94,6 +164,10 @@ Registrácia zlyhala Užívateľské meno už existuje Registrácia ukončená + Registrácia nie je podporovaná serverom. + Neplatný registračný token + Doména sa nedá overiť + Porušenie pravidiel Nekompatibilný server Nezašifrovaný OTR @@ -106,8 +180,13 @@ Povoliť účet Ste si istý? Nahrať hlas + XMPP adresa + Zablokovať adresu XMPP meno@priklad.com Heslo + Toto nie je platná XMPP adresa + Nedostatok pamäte. Obrázok príliš veľký + Chcete pridať do vašich kontaktov %s? Informácie o serveri XEP-0313: MAM XEP-0280: Message Carbons @@ -115,38 +194,65 @@ XEP-0191: Blocking Command XEP-0237: Roster Versioning XEP-0198: Stream Management + XEP-0215: Zistenie externej služby XEP-0163: PEP (Avatars / OMEMO) XEP-0363: HTTP File Upload + XEP-0357: Oznámenia dostupný nedostupný Chýba oznámenie o verejnom kľúči práve prihlásený + naposledy videný pred minútou naposledy prihlásený pred %d minútami + naposledy videný pred hodinou naposledy prihlásený pred %d hodinami + naposledy videný včera naposledy prihlásený pred %d dňami OMEMO identifikátor + v\\OMEMO odtlačok + OMEMO odtlačok (pôvod správy) + v\\OMEMO odtlačok (pôvod správy) Ostatné zariadenia Dôverovať OMEMO identifikátoru + Načítavam kľúče... Dokončený Dešifrovať + Záložky Hľadať + Zmazať kontakt Zobraziť detaily kontaktu Zablokovať kontakt Odblokovať kontakt Vytvoriť + Vybrať Kontakt už existuje Vstúpiť + channel@conference.example.com/nick + channel@conference.example.com Uložiť ako záložku Vymazať záložku + Vymazať skupinový rozhovor + Vymazať kanál + Nemohol som vymazať skupinový rozhovor + Nemohol som vymazať kanál + Upraviť predmet skupinového rozhovoru + Téma + Pripájam skupinový rozhovor... Odísť Kontakt pridaný do zoznamu Znova pridať %s dočítal až potiaľ + %sdočítal potiaľto + %1$s+%2$dostatní dočítali potiaľto + Každý dočítal potiaľto Zverejniť + Ťuknite na avatar pre vybranie obrázka z galérie Zverejňujem… Server odmietol toto zverejnenie + Nemôžem skonvertovať váš obrázok Nepodarilo sa uložiť avatar na disk (Dlho podržať pre obnovenie pôvodného stavu) + Váš server nepodporuje zverejnenie avatarov súkromná správa pre %s Odoslať súkromnú správu %s @@ -159,26 +265,50 @@ Vložiť heslo Ihneď vyžiadať Ignorovať + Bezpečnosť + Povoliť úpravu správy + Povoliť vašim kontaktom spätne upraviť ich správy + Nastavenia pre skúsených S týmto narábajte veľmi opatrne, prosím + O %s Tichý režim Čas začiatku Čas konca Povoliť tichý režim Upozornenia budú počas tichého režimu stlmené Ďalší + Synchronizovať so záložkami + Automaticky sa pripojiť k skupinovému rozhovoru, ak to hovorí záložka + OMEMO odtlačok skopírovaný do schránky + Ste zakázaný na tomto skupinovom rozhovore + Skupinový rozhovor len pre členov + Skupinový rozhovor bol zastavený + Už viac nie ste v tomto skupinovom rozhovore Používa sa účet %s + Hostovaný na %s Overiť %s na HTTP host Nie ste pripojený. Skúste to neskôr Overiť %s veľkosť + Skontrolujte %1$sveľkosť na %2$s Možnosti správy + Citovať + Vložiť ako citát Skopírovať originálny URL Poslať znova URL súbor + URL skopírovaná do schránky + XMPP adresa skopírovaná do schránky + Správa o chybe skopírovaná do schránky + web adresa + Snímať 2D Bar kód + Ukázať 2D Bar kód Zobraziť zoznam blokovaných Detaily účtu Potvrdiť Skúste znova Zamedzí operačnému systému ukončiť pripojenie + Vytvoriť zálohu + Vaša záloha bola obnovená Vybrať súbor Prijímam %1$s (%2$d%% ukončený) Stiahnuť %s @@ -191,6 +321,9 @@ Povoliť upozornenia Avatar účtu Skopírovať OMEMO identifikátor do schránky + Regenerovať OMEMO kľúč + Vymazať zariadenia + Ste si istý, že chcete odstrániť všetky ostatné zariadenia z OMEMO oznámenia? Keď sa nabudúce vaše zariadenia pripoja, znova sa samé ohlásia, ale nemusia prijať správy odoslané medzitým. Načítať históriu zo serveru Na serveri nie je žiadna ďalšia história Aktualizujem... @@ -257,7 +390,48 @@ Užívateľské meno Toto nie je platné užívateľské meno Obnoviť certifikát + Chyba pri načítaní OMEMO kľúča! + kľúč OMEMO overený certifikátom! + Pripojiť cez Tor + %1$dz%2$dúčtov pripojených + (Žiadne aktivované účty) Online + Vymazať OMEMO identifikátory + Re-generuje vaše kľúče OMEMO. Všetky vaše kontakty vás budú musieť znova overiť. Použite to ako poslednú možnosť. + Overili ste všetky kľúče OMEMO vo vašom vlastníctve. + Overiť kľúče OMEMO + online práve teraz Správa skopírovaná do schránky + OMEMO šifrovanie + OMEMO bude vždy používané pre individuálne a súkromné skupinové rozhovory. + OMEMO bude predvolene zapnuté pre všetky rozhovory. + Veľkosť písma + Nepodarilo sa dešifrovať OMEMO správu. Zobraziť polohu + Hovory + Prichádzajúce hovory + Prebiehajúce hovory + Nastavenia oznámení prichádzajúcich hovorov + Obnoviť zálohu + Priložiť + Prichádzajúci hovor + Prichádzajúci video hovor + Prijímam hovor + Ukončujem hovor + Vyhľadávanie zariadení + Zvoní + Nedá sa pripojiť hovor + Prebiehajúci hovor + Prebiehajúci video hovor + Prichádzajúci hovor + Prichádzajúci hovor - %s + Zmeškaný hovor - %s + Odchádzajúci hovor + Odchádzajúci hovor - %s + Zmeškaný hovor + Hlasový hovor + Video hovor + Naraz môžete mať iba jeden hovor. + Vrátiť sa do prebiehajúceho hovoru + Zašifrované s OMEMO diff --git a/src/main/res/values-sr/strings.xml b/src/main/res/values-sr/strings.xml index 623bb6a94..cd39b9369 100644 --- a/src/main/res/values-sr/strings.xml +++ b/src/main/res/values-sr/strings.xml @@ -111,7 +111,9 @@ нудим… чекам… Нема ОпенПГП кључа + Није могуће шифровати вашу поруку јер контакт није објавио свој јавни кључ.\n\nЗамолите контакт да подеси ОпенПГП. Нема ОпенПГП кључева + Није могуће шифровати вашу поруку јер контакти нису објавили своје јавне кључеве.\n\nЗамолите контакте да подесе ОпенПГП. Опште Прихватај фајлове Аутоматски прихватај фајлове мање од… @@ -128,6 +130,7 @@ Период одгоде Напредно Никад не шаљи извештаје о паду + Слањем извештаја рада помажете развоју апликације. Потврди поруке Обзнаните контактима када примите и прочитате њихове поруке Сучеље @@ -147,6 +150,8 @@ Не могу преобратити датотеку фотографије Фајл није нађен Општа У/И грешка. Можда вам је нестало простора у складишту? + Апликација из које делите ову слику не даје дозволу довољну да се датотека учита.\n\nПоделите слику другим претраживачем датотека. + Апликација из које делите овај садржај не даје довољну дозволу. Непознато Привремено искључен На вези @@ -179,6 +184,7 @@ ОпенПГП кључ је објављен. Укључи налог Да ли сте сигурни? + Брисањем налога бришете и целу историју ваших разговора. Сними глас ИксМПП адреса Блокирај ИксМПП адресу @@ -237,6 +243,8 @@ Обриши обележивач Уклони групно ћаскање Уклони канал + Да ли сигурно жеите да уклоните ово групно ћаскање?\n\nУпозорење: Групно ћаскање ће бити потпуно обрисано са сервера. + Да ли сигурно жеите да уклоните овај јавни канал?\n\nУпозорење: Канал ће бити потпуно обрисан са сервера. Не могу уклонити групно ћаскање Не могу уклонити канал Уреди предмет групног ћаскања @@ -269,8 +277,10 @@ Укључи Групно ћаскање захтева лозинку Унесите лозинку + Најпре захтевајте ажурирање присутности од вашег контакта.\n\nОво ће омогућити да се одреди који клијент ваш контакт користи. Захтевај одмах Занемари + Упозорење: Слањем овога без обостраног ажурирања присутности може изазвати неочекиване проблеме.\n\nИдите у „Детаљи контакта” да потврдите вашу претплату за присутност. Безбедност Дозволи исправљање порука Дозвољава вашим контактима да ретроактивно уређују њихове поруке @@ -349,6 +359,7 @@ Копирај ОМЕМО отисак на клипборд Поново генериши ОМЕМО кључ Очисти уређаје + Нема употребљивих кључева за овај контакт.\nПроверите да ли сте одобрили узајамно ажурирање присутности. Нешто је пошло по злу Добављам историјат са сервера Нема више историјата на серверу @@ -515,6 +526,7 @@ Исправи поруку Пошаљи исправљену поруку Искључили сте овај налог + Безбедносна грешка: неисправан приступ датотеци! Нема апликације за дељење ресурса Подели везу помоћу… Сложи се и настави @@ -523,6 +535,7 @@ Користићу сопствени провајдер Одредите ваше корисничко име Ручно мењај доступност + Поставите присутност при измени ваше поруке стања. Порука стања Слободан за ћаскање На вези @@ -564,12 +577,17 @@ Дозволи Нема дозвола за приступ %s Удаљени сервер није нађен + Удаљени сервер се не одазива + Не могу да ажурирам налог + Пријавите ову ИксМПП адресу због нежељених порука. Обриши ОМЕМО идентитете Обриши изабране кључеве Морате бити повезани да бисте објавили ваш аватар. Прикажи поруку грешке Порука грешке Чувар протока укључен + Ваш уређај не подржава искључење „Уштеде података” за %1$s. + Не могу да направим привремену датотеку Овај уређај је оверен. Копирај отисак Оверени отисци diff --git a/src/main/res/values-sv/strings.xml b/src/main/res/values-sv/strings.xml index 595a6a345..96b77ff57 100644 --- a/src/main/res/values-sv/strings.xml +++ b/src/main/res/values-sv/strings.xml @@ -4,6 +4,7 @@ Ny konversation Kontoinställningar Hantera konto + Stäng konversation Kontaktdetaljer Gruppchattdetaljer Kanaldetaljer @@ -28,6 +29,13 @@ just nu 1 min sedan %d min sedan + + %d oläst konversation + + + %d olästa konversationer + + skickar… Avkrypterar meddelande. Vänta… OpenPGP-krypterat meddelande @@ -105,12 +113,16 @@ LED notifieringar Blinka med notifieringsljuset när ett meddelande tagits emot Meddelandesignal + Aviseringsljud + Aviseringsljud för nya meddelande + Ringsignal för inkommande samtal Notifieringsfrist Avancerat Skicka aldrig krasch-rapporter Bekräfta meddelanden Låt dina kontakter veta när du har mottagit och läst deras meddelanden Gränssnitt + OpenKeychain genererade ett fel. Dålig krypterings-nyckel. Acceptera Ett fel har inträffat @@ -136,6 +148,7 @@ Registreringsfel Användarnamn används redan Registrering klar + Registrering stöds ej av server TLS-förhandling misslyckades Kränkning av policy Inkompatibel server @@ -173,9 +186,14 @@ otillgänglig Annonsering om publik nyckel saknas senast sedd just nu + senast sedd för en minut sedan senast sedd %d minuter sedan + senast sedd för en timme sedan senast sedd %d timmar sedan + senast sedd för en dag sedan senast sedd %d dagar sedan + Krypterat meddelande. Installera OpenKeychain för att dekryptera meddelandet. + Nytt OpenPGP krypterat meddelande hittades OpenPGP-nyckel-ID OMEMO-fingeravtryck v\\OMEMO-fingeravtryck @@ -500,7 +518,7 @@ Krypterar meddelande Hämtar inte meddelanden på grund av inställningen för borttagning av gamla meddelanden. Komprimerar video - Motsvarande konversationer är stängda. + Korresponderande konversationer är stängda. Kontakt blockerad. Notifieringar från främlingar Mottagna meddelanden från främlingar @@ -531,6 +549,7 @@ Visa plats Dela Var god dröj... + Söka i meddelanden GIF Kopiera XMPP-adress Smeknamn @@ -557,7 +576,10 @@ e-bok Öppna med... Välj konto + Återställa säkerhetskopiering + Återställa Ange ditt lösenord till kontot %s för att återställa säkerhetskopian. + Det gick inte att återställa säkerhetskopian. Skapa gruppchatt Skapa sluten gruppchatt Kanalnamn @@ -584,4 +606,7 @@ Om Aktivera ett konto Upptagen + Fäst flik till toppen + Ta bort flik från toppen + Fler alternativ diff --git a/src/main/res/values-tr-rTR/strings.xml b/src/main/res/values-tr-rTR/strings.xml index 3460ee293..4f0084189 100644 --- a/src/main/res/values-tr-rTR/strings.xml +++ b/src/main/res/values-tr-rTR/strings.xml @@ -150,6 +150,7 @@ Dosya bulunamadı Genel G/Ç hatası. Depolama yeri kalmamış olabilir mi? Bu görüntüyü seçmekte kullandığınız uygulama, dosyanın okunması için yeterli izinleri sağlayamadı. Görüntüyü seçmek için farklı bir dosya yöneticisi kullan. + Bu dosyayı paylaşmakta kullandığınız uygulama yeterince yetki sağlamamaktadır. Bilinmeyen Geçici olarak devre dışı Çevrim içi @@ -164,6 +165,7 @@ Hesap, sunucu tarafından desteklenmiyor. Geçersiz hesak simgesi TLS uzlaşması başarısız + Alan adı doğrulanamıyor Politika ihlali Sunucu uyuşmazlığı Akış hatası @@ -959,4 +961,6 @@ Davet iletilemedi Sunucu, davet oluşturulmasını desteklemiyor Bu özelliği destekleyen aktif bir hesap yok + Yedekleme başlatıldı. Tamamlandığı zaman bir bildirim alacaksınız. + Video etkinleştirilemedi diff --git a/src/main/res/values-vi/strings.xml b/src/main/res/values-vi/strings.xml index deebac8f8..31cd4d735 100644 --- a/src/main/res/values-vi/strings.xml +++ b/src/main/res/values-vi/strings.xml @@ -504,7 +504,7 @@ Đã chia sẻ các hình ảnh với %s Đã chia sẻ văn bản với %s Cấp quyền truy cập bộ nhớ cho %1$s - Cấp quyền truy cập camera cho %1$s + Cấp quyền truy cập máy ảnh cho %1$s Đồng bộ với danh bạ %1$s muốn quyền truy cập sổ địa chỉ của bạn để nối nó với danh sách liên hệ XMPP của bạn.\nViệc này sẽ hiển thị họ tên và ảnh đại diện của các liên hệ của bạn.\n\n%1$s sẽ chỉ đọc sổ địa chỉ của bạn và nối nó một cách cục bộ mà không tải gì cả lên máy chủ của bạn.
Chúng tôi sẽ không lưu trữ bản sao của các số điện thoại đó.\n\nĐể biết thêm thông tin hãy đọc chính sách riêng tư của chúng tôi.

Bây giờ bạn sẽ được hỏi cấp quyền truy cập danh bạ.]]>
@@ -530,11 +530,424 @@ Lỗi bảo mật: Truy cập tệp không hợp lệ! Không tìm thấy ứng dụng nào để chia sẻ URI Chia sẻ URI với... +
Bạn đăng ký bằng số điện thoại của bạn và Quicksy sẽ tự động—dựa trên những số điện thoại trong sổ địa chỉ của bạn—đề xuất các liên hệ có thể có cho bạn.

Bằng cách đăng ký, bạn đồng ý với chính sách riêng tư của chúng tôi.]]>
Đồng ý và tiếp tục + Một hướng dẫn đã được thiết lập cho việc tạo tài khoản trên conversations.im.¹\nKhi chọn conversations.im làm nhà cung cấp, bạn sẽ có thể giao tiếp với những người dùng của các nhà cung cấp khác bằng cách đưa cho họ địa chỉ XMPP đầy đủ của bạn. Địa chỉ XMPP đầy đủ của bạn sẽ là: %s Tạo tài khoản + Dùng nhà cung cấp của tôi + Hãy chọn tên người dùng + Quản lý tính khả dụng thủ công + Đặt tính khả dụng của bạn khi chỉnh sửa thông báo trạng thái của bạn. + Thông báo trạng thái + Rảnh để trò chuyện Trực tuyến + Vắng mặt + Không khả dụng + Bận + Một mật khẩu bảo mật đã được tạo + Thiết bị của bạn không hỗ trợ tắt tối ưu hoá pin + Đăng ký thất bại: Hãy thử lại sau + Đăng ký thất bại: Mật khẩu quá yếu + Chọn các thành viên + Tạo nhóm chat... + Mời lại Tắt + Ngắn + Vừa + Dài + Sử dụng truyền phát + Cho các liên hệ của bạn biết bạn dùng Conversations + Riêng tư + Chủ đề + Chọn bộ màu sáng + Tự động + Sáng + Tối + Nền xanh lá cây + Dùng nền xanh lá cây cho tin nhắn nhận được + Không thể kết nối với OpenKeychain + Thiết bị này không còn được dùng nữa + Máy tính + Điện thoại di động + Máy tính bảng + Trình duyệt web + Bảng điều khiển + Yêu cầu thanh toán + Cho phép sử dụng Internet + Tôi + Liên hệ yêu cầu đăng ký sự có mặt + Cho phép + Không có quyền truy cập%s + Không tìm thấy máy chủ trên mạng + Hết thời gian chờ cho máy chủ trên mạng + Không thể cập nhật tài khoản + Báo cáo địa chỉ XMPP này vì spam. + Xoá các danh tính OMEMO + Tái tạo lại các mã khoá OMEMO của bạn. Tất cả các liên hệ của bạn sẽ phải xác minh lại bạn. Chỉ sử dụng việc này làm giải pháp cuối cùng. + Xoá các mã khoá đã chọn + Bạn cần phải có kết nối để xuất bản ảnh đại diện của bạn. + Hiện thông báo lỗi + Thông báo lỗi + Trình tiết kiệm dữ liệu đang bật + Hệ điều hành của bạn đang giới hạn %1$s truy cập Internet trong nền. Để nhận các thông báo tin nhắn mới, bạn nên cho phép %1$s truy cập không giới hạn khi \"Trình tiết kiệm dữ liệu\" đang bật.\n%1$s vẫn sẽ nỗ lực tiết kiệm dữ liệu khi có thể. + Thiết bị của bạn không hỗ trợ việc tắt Trình tiết kiệm dữ liệu cho %1$s. + Không thể tạo tệp tạm + Thiết bị này đã được xác thực + Sao chép mã vân tay + Bạn đã xác minh tất cả mã khoá OMEMO mà bạn đang sở hữu + Mã vạch không chứa mã vân tay cho cuộc trò chuyện này. + Mã vân tay đã xác minh + Sử dụng máy ảnh để quét mã vạch của liên hệ + Vui lòng đợi để lấy các mã khoá + Chia sẻ dưới dạng mã vạch + Chia sẻ dưới dạng URI XMPP + Chia sẻ dưới dạng liên kết HTTP + Tin tưởng mù quáng trước khi xác minh + Tin tưởng các thiết bị mới từ các liên hệ chưa xác minh, nhưng hỏi xác nhận thủ công các thiết bị mới từ các liên hệ đã xác minh. + Các mã khoá OMEMO đã tin tưởng mù quáng, có nghĩa là họ có thể là một ai đó khác hoặc ai đó có thể đã can thiệp. + Chưa tin tưởng + Mã vạch 2D không hợp lệ + Dọn dẹp thư mục bộ nhớ tạm (được ứng dụng máy ảnh sử dụng) + Dọn dẹp bộ nhớ tạm + Dọn dẹp bộ nhớ riêng + Dọn dẹp bộ nhớ riêng nơi các tệp được giữ (Chúng có thể được tải xuống lại từ máy chủ) + Tôi đã đi theo liên kết này từ một nguồn được tin tưởng + Bạn sắp xác minh các mã khoá OMEMO của %1$s sau khi nhấn vào một liên kết. Việc này chỉ là bảo mật nếu bạn đã đi theo liên kết này từ một nguồn được tin tưởng, nơi chỉ có %2$s có thể đã xuất bản liên kết này. + Xác minh các mã khoá OMEMO + Hiện không hoạt động + Ẩn không hoạt động + Huỷ tin tưởng thiết bị + Bạn có chắc bạn muốn bỏ xác minh thiết bị này không?\nThiết bị này và các tin nhắn từ nỏ sẽ được đánh dấu là \"Chưa tin tưởng\". + + %d giây + + + %d phút + + + %d giờ + + + %d ngày + + + %d tuần + + + %d tháng + + Tự động xoá tin nhắn + Tự động xoá các tin nhắn cũ hơn phạm vi thời gian được thiết lập khỏi thiết bị. + Đang mã hoá tin nhắn + Không lấy tin nhắn do khoảng thời gian giữ lại cục bộ. + Đang nén video + Đã đóng các cuộc hội thoại tương ứng. + Đã chặn liên hệ. + Thông báo từ người lạ + Thông báo về các tin nhắn và cuộc gọi được nhận từ những người lạ. + Đã nhận tin nhắn từ người lạ + Chặn người lạ + Chặn toàn bộ miền + trực tuyến ngay lúc này + Thử giải mã lại + Lỗi phiên làm việc + Cơ chế SASL đã bị hạ cấp + Máy chủ yêu cầu đăng ký trên trang web + Mở trang web + Không tìm thấy ứng dụng nào để mở trang web + Thông báo gây chú ý + Hiện thông báo gây chú ý + Hôm nay + Hôm qua + Xác thực tên máy chủ bằng DNSSEC + Các chứng chỉ máy chủ chứa tên miền được xác thực được coi là đã xác minh + Chứng chỉ không chứa địa chỉ XMPP hợp lệ + một phần + Ghi video + Sao chép vào bộ nhớ tạm Đã chép tin nhắn vào clipboard + Tin nhắn + Tin nhắn riêng tư bị tắt + Ứng dụng được bảo vệ + Để tiếp tục nhận các thông báo, kể cả khi màn hình đã tắt, bạn cần thêm Conversations vào danh sách các ứng dụng được bảo vệ. + Chấp nhận chứng chỉ không xác định? + Chứng chỉ máy chủ này không được một người có quyền chứng chỉ đã biết ký. + Chấp nhận tên máy chủ không khớp? + Máy chủ không thể xác thực với tư cách \"%s\". Chứng chỉ chỉ hợp lệ cho: + Bạn có muốn vẫn kết nối không? + Chi tiết chứng chỉ: + Một lần + Trình quét mã QR cần quyền truy cập máy ảnh + Cuộn xuống dưới cùng + Cuộn xuống sau khi gửi một tin nhắn + Chỉnh sửa thông báo trạng thái + Chỉnh sửa thông báo trạng thái + Tắt mã hoá + %1$s không thể gửi tin nhắn được mã hoá đến %2$s. Điều này có thể là do liên hệ của bạn sử dụng một máy chủ hoặc ứng dụng khách lỗi thời không thể xử lý OMEMO. + Không thể lấy danh sách thiết bị + Không thể lấy mã khoá mã hoá + Gợi ý: Trong một số trường hợp, điều này có thể được sửa bằng cách thêm lẫn nhau vào danh sách liên hệ của bạn. + Bạn có chắc bạn muốn tắt mã hoá OMEMO cho cuộc hội thoại này không?\nViệc này sẽ cho phép quản trị viên máy chủ đọc các tin nhắn của bạn, nhưng việc này có thể là cách duy nhất để giao tiếp với những người sử dụng các ứng dụng khách lỗi thời. + Tắt ngay + Bản nháp: + Mã hoá OMEMO + OMEMO sẽ luôn được sử dụng cho các cuộc trò chuyện nhóm một đối một và riêng tư. + OMEMO sẽ được sử dụng theo mặc định cho các cuộc hội thoại mới. + OMEMO sẽ phải được bật một cách rõ ràng cho các cuộc hội thoại mới. + Tạo lối tắt + Cỡ chữ + Cỡ chữ tương đối được sử dụng trong ứng dụng. + Bật theo mặc định + Tắt theo mặc định + Nhỏ + Trung bình + Lớn + Tin nhắn đã không được mã hoá cho thiết bị này. + Giải mã tin nhắn OMEMO thất bại. + hoàn tác + Chia sẻ vị trí bị tắt + Cố định vị trí + Bỏ cố định vị trí + Sao chép vị trí + Chia sẻ vị trí + Hướng + Chia sẻ vị trí Hiện vị trí + Chia sẻ + Không thể bắt đầu ghi lại + Vui lòng đợi... + Cấp quyền truy cập micro cho %1$s + Tìm kiếm tin nhắn + GIF + Xem cuộc hội thoại + Chia sẻ plugin vị trí + Sử dụng plugin chia sẻ vị trí thay vì bản đồ được tích hợp + Sao chép địa chỉ web + Sao chép địa chỉ XMPP + Chia sẻ tệp HTTP cho S3 + Tìm kiếm trực tiếp + Tại màn hình \'Bắt đầu cuộc hội thoại\', mở bàn phím và đặt con trỏ trong trường tìm kiếm + Ảnh đại diện cuộc trò chuyện nhóm + Máy chủ không hỗ trợ ảnh đại diện cuộc trò chuyện nhóm + Chỉ có chủ sở hữu mới có thể thay đổi ảnh đại diện cuộc trò chuyện nhóm + Tên liên hệ + Biệt danh + Tên + Việc cung cấp tên là không bắt buộc + Tên cuộc trò chuyện nhóm + Cuộc trò chuyện nhóm này đã bị phá huỷ + Không thể lưu bản ghi + Dịch vụ ở trước + Hạng mục thông báo này được sử dụng để hiển thị một thông báo vĩnh viễn chỉ ra rằng %1$s đang chạy. + Thông tin trạng thái + Vấn đề kết nối + Hạng mục thông báo này được sử dụng để hiển thị một thông báo trong trường hợp có vấn đề khi kết nối đến một tài khoản. + Tin nhắn + Cuộc gọi + Tin nhắn + Cuộc gọi đến + Cuộc gọi đang diễn ra + Tin nhắn im lặng + Nhóm thông báo này được sử dụng để hiển thị các thông báo không nên phát ra tiếng động. Ví dụ là khi đang hoạt động trên một thiết bị khác (thời gian ân hạn). + Gửi đi thất bại + Cài đặt thông báo tin nhắn + Cài đặt thông báo cuộc gọi đến + Sự quan trọng, âm thanh, rung + Nén video + Xem phương tiện + Thành viên + Trình duyệt phương tiện + Tệp đã bị bỏ vì vi phạm bảo mật. + Chất lượng video + Chất lượng thấp hơn có nghĩa là tệp nhỏ hơn + Trung bình (360p) + Cao (720p) + đã huỷ + Bạn đã đang tạo bản nháp một tin nhắn rồi. + Tính năng chưa được thêm + Mã quốc gia không hợp lệ + Chọn quốc gia + số điện thoại + Xác minh số điện thoại của bạn + Quicksy sẽ gửi một tin nhắn SMS (có thể áp dụng phí nhà mạng) để xác minh số điện thoại của bạn. Hãy nhập mã quốc gia và số điện thoại của bạn: +
%s

Điều này có ổn không, hay bạn muốn chỉnh sửa số điện thoại?]]>
+ %s không phải là số điện thoại hợp lệ. + Vui lòng nhập số điện thoại của bạn. + Tìm kiếm quốc gia + Xác minh %s + %s.]]> + Chúng tôi đã gửi một SMS khác có mã 6 chữ số cho bạn. + Vui lòng nhập mã PIN 6 chữ số ở dưới. + Gửi lại SMS + Gửi lại SMS (%s) + Vui lòng đợi (%s) + quay lại + Đã tự động dán mã PIN có thể có từ bộ nhớ tạm. + Vui lòng nhập mã PIN 6 chữ số. + Bạn có chắc bạn muốn huỷ quá trình đăng ký không? + + Không + Đang xác minh... + Đang yêu cầu SMS... + Mã PIN bạn đã nhập không chính xác. + Mã PIN chúng tôi gửi cho bạn đã hết hạn. + Lỗi mạng không xác định. + Phản hồi không xác định từ máy chủ. + Không thể kết nối đến máy chủ. + Không thể lập kết nối bảo mật. + Không thể tìm máy chủ. + Có gì đó sai đã xảy ra khi xử lý yêu cầu của bạn. + Đầu vào người dùng không hợp lệ + Tạm thời không có sẵn. Hãy thử lại sau. + Không có kết nối mạng. + Vui lòng thử lại trong %s + Bạn bị giới hạn tốc độ + Quá nhiều lần thử + Bạn đang sử dụng một phiên bản lỗi thời của ứng dụng này. + Cập nhật + Số điện thoại này hiện đã được đăng nhập ở một thiết bị khác. + Vui lòng nhập tên của bạn để cho những người không có bạn trong sổ địa chỉ của họ biết bạn là ai. + Tên của bạn + Nhập tên của bạn + Sử dụng nút chỉnh sửa để đặt tên của bạn. + Từ chối yêu cầu + Cài đặt Orbot + Khởi động Orbot + Không có ứng dụng chợ nào được cài đặt. + Kênh này sẽ làm cho địa chỉ XMPP của bạn trở thành công khai + sách điện tử + Gốc (không nén) + Mở bằng... + Ảnh hồ sơ Conversations + Chọn tài khoản + Khôi phục bản sao lưu + Khôi phục + Nhập mật khẩu của bạn cho tài khoản %s để khôi phục bản sao lưu. + Đừng sử dụng tính năng khôi phục bản sao lưu để cố gắng nhân bản (chạy đồng thời) một lượt cài đặt. Việc khôi phục một bản sao lưu chỉ dành cho việc di cư hoặc trong trường hợp bạn đã mất thiết bị gốc. + Không thể khôi phục bản sao lưu. + Không thể giải mã bản sao lưu. Mật khẩu có đúng không? + Sao lưu & khôi phục + Nhập địa chỉ XMPP + Tạo cuộc trò chuyện nhóm + Tham gia kênh công khai + Tạo cuộc trò chuyện nhóm riêng tư + Tạo kênh công khai + Tên kênh + Địa chỉ XMPP + Vui lòng cung cấp tên cho kênh + Vui lòng cung cấp địa chỉ XMPP + Đây là một địa chỉ XMPP. Vui lòng cung cấp một cái tên. + Đang tạo kênh công khai... + Kênh này đã tồn tại + Bạn đã tham gia một kênh đang tồn tại + Không thể lưu thiết lập kênh + Cho phép bất kỳ ai chỉnh sửa chủ đề + Cho phép bất kỳ ai mời những người khác + Bất kỳ ai cũng có thể chỉnh sửa chủ đề. + Chủ sở hữu có thể chỉnh sửa chủ đề. + Quản trị viên có thể chỉnh sửa chủ đề. + Chủ sở hữu có thể mời những người khác. + Bất kỳ ai cũng có thể mời những người khác. + Các địa chỉ XMPP có thể được quản trị viên nhìn thấy. + Các địa chỉ XMPP có thể được bất kỳ ai nhìn thấy. + Kênh công khai này không có thành viên nào. Hãy mời các liên hệ của bạn hoặc sử dụng nút chia sẻ để phân phát địa chỉ XMPP của kênh. + Cuộc trò chuyện nhóm riêng tư này không có thành viên nào. + Quản lý đặc quyền + Tìm kiếm thành viên + Tệp quá lớn + Đính kèm + Khám phá các kênh + Tìm kiếm kênh + Sự vi phạm tính riêng tư có thể có! + search.jabber.network.

Việc sử dụng tính năng này sẽ truyền địa chỉ IP và câu từ tìm kiếm của bạn đến dịch vụ đó. Hãy xem Chính sách riêng tư của họ để biết thêm thông tin.]]>
+ Tôi đã có một tài khoản rồi + Thêm tài khoản đang tồn tại + Đăng ký tài khoản mới + Cái này trông giống một địa chỉ miền + Vẫn thêm + Cái này trông giống một địa chỉ kênh + Chia sẻ tệp sao lưu + Bản sao lưu Conversations + Sự kiện + Mở bản sao lưu + Tệp bạn đã chọn không phải là tệp sao lưu của Conversations + Tài khoản này đã được thiết lập rồi + Vui lòng nhập mật khẩu cho tài khoản này + Không thể thực hiện hành động này + Tham gia kênh công khai... + Ứng dụng chia sẻ đã không cấp quyền truy cập tệp này. + + jabber.network + Máy chủ cục bộ + Đa số người dùng nên chọn \'jabber.network\' để có những đề xuất tốt hơn từ toàn thể hệ sinh thái XMPP. + Phương pháp khám phá kênh + Sao lưu + Giới thiệu + Vui lòng bật một tài khoản + Tạo cuộc gọi + Cuộc gọi đến + Cuộc gọi video đến + Đang kết nối + Đã kết nối + Đang chấp nhận cuộc gọi + Đang kết thúc cuộc gọi + Trả lời + Từ chối + Đang khám phá các thiết bị + Đang đổ chuông + Bận + Không thể kết nối cuộc gọi + Đã mất kết nối + Cuộc gọi đã bị rút lại + Lỗi ứng dụng + Cúp máy + Cuộc gọi đang diễn ra + Cuộc gọi video đang diễn ra + Tắt Tor để tạo cuộc gọi + Cuộc gọi đến + Cuộc gọi đến · %s + Cuộc gọi nhỡ · %s + Cuộc gọi đi + Cuộc gọi đi · %s + Cuộc gọi nhỡ + Cuộc gọi âm thanh + Cuộc gọi video + Trợ giúp + Chuyển sang cuộc hội thoại + Micro của bạn không có sẵn + Bạn chỉ có thể có một cuộc gọi trong một lúc. + Quay lại cuộc gọi đang diễn ra + Không thể chuyển máy ảnh + Ghim lên đầu + Bỏ ghim khỏi đầu + Tuyến đường GPS + Không thể sửa tin nhắn + Tất cả cuộc hội thoại + Cuộc hội thoại này + Ảnh đại diện của bạn + Ảnh đại diện cho %s + Được mã hoá bằng OMEMO + Được mã hoá bằng OpenPGP + Không được mã hoá + Thoát + Ghi lại tin nhắn thoại + Phát âm thanh + Tạm dừng âm thanh + Thêm liên hệ, tạo hoặc tham gia cuộc trò chuyện nhóm, hoặc khám phá các kênh + + Xem %1$d thành viên + + + Một số tin nhắn không thể được gửi + + Gửi đi thất bại + Thêm tuỳ chọn + Không tìm thấy ứng dụng nào + Mời vào Conversations + Không thể xử lý lời mời + Máy chủ không hỗ trợ tạo lời mời + Không có tài khoản đang hoạt động nào hỗ trợ tính năng này + Việc sao lưu đã được bắt đầu. Bạn sẽ nhận một thông báo khi việc đó đã hoàn tất. + Không thể bật video. diff --git a/src/main/res/values-zh-rCN/strings.xml b/src/main/res/values-zh-rCN/strings.xml index 4ef9ecc32..4df82f547 100644 --- a/src/main/res/values-zh-rCN/strings.xml +++ b/src/main/res/values-zh-rCN/strings.xml @@ -950,4 +950,4 @@ 没有活跃帐户支持此功能 已启动备份。一旦完成,你会收到通知。 无法启用视频 - + diff --git a/src/quicksy/res/values-ar/strings.xml b/src/quicksy/res/values-ar/strings.xml index d33b9d020..d0af2d68c 100644 --- a/src/quicksy/res/values-ar/strings.xml +++ b/src/quicksy/res/values-ar/strings.xml @@ -1,6 +1,12 @@ + طول الوقت الذي يبقى فيه كويكسي صامتا بعد رؤية النشاط في جهاز آخر + عبر إرسال أثار الأخطاء تقوم بالمساعدة في تطوير برمجة كويكسي إجعل كلّ جهات إتصالك تعلم أنك تستعمل كويكسي + للمواصلة في إستقبال التنبيهات، حتى والشاشة مغلقة، يجب عليك أن تضيف كويكسي إلى قائمة التطبيقات المحميّة. صورة حساب كويكسي إن كويكسي Quicksy غير متوفر في بلدكم. - + لا يمكن التأكد من خادم الهويّة. + خطأ أمني مجهول. + تجاوز الوقت أثناء الإتصال بالخادم. + From af42e3465456155e5cecb1672faca78ab2224bc2 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 24 Aug 2021 16:53:23 +0200 Subject: [PATCH 56/82] Revert "Always show Quote as last action" This reverts commit e528b9f5df59f7b49ae18c73396bd56525493e28. I was originally convinced by the argumentation (quote always in same place) but testing this out for a while really seems to break 'last correct' for me. I use that way more frequently that quote --- src/main/res/menu/message_context.xml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/res/menu/message_context.xml b/src/main/res/menu/message_context.xml index f6592c5af..f32505203 100644 --- a/src/main/res/menu/message_context.xml +++ b/src/main/res/menu/message_context.xml @@ -20,6 +20,10 @@ android:id="@+id/copy_link" android:title="@string/copy_link" android:visible="false" /> + - - + \ No newline at end of file From 581eb511b98eb0cc709790f0c100a3f37643e2d5 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 24 Aug 2021 18:48:50 +0200 Subject: [PATCH 57/82] version bump to 2.10.0-beta --- CHANGELOG.md | 5 +++++ build.gradle | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9da1fec84..90bf2a78e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +### Version 2.10.0 + +* Show black bars when remote video does not match aspect ratio of screen +* Add setting to prevent screenshots + ### Version 2.9.13 * minor A/V improvements diff --git a/build.gradle b/build.gradle index 7720d848a..329614436 100644 --- a/build.gradle +++ b/build.gradle @@ -93,8 +93,8 @@ android { defaultConfig { minSdkVersion 21 targetSdkVersion 29 - versionCode 42015 - versionName "2.9.13" + versionCode 42016 + versionName "2.10.0-beta" archivesBaseName += "-$versionName" applicationId "eu.siacs.conversations" resValue "string", "applicationId", applicationId From f975b5ddacb4e47f6ce68afc708e8a68aa3f63f1 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 25 Aug 2021 18:54:00 +0200 Subject: [PATCH 58/82] executePendingTransactions before trying to access secondary_fragment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If we don’t executePendingTransactions we might still access the overview fragment while a replacement operation is in the works. This will lead to two conversationfragments opening. --- .../ui/ConversationsActivity.java | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java index e3f52aa21..86f49064a 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java @@ -425,16 +425,18 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio } private void openConversation(Conversation conversation, Bundle extras) { - ConversationFragment conversationFragment = (ConversationFragment) getFragmentManager().findFragmentById(R.id.secondary_fragment); + final FragmentManager fragmentManager = getFragmentManager(); + fragmentManager.executePendingTransactions(); + ConversationFragment conversationFragment = (ConversationFragment) fragmentManager.findFragmentById(R.id.secondary_fragment); final boolean mainNeedsRefresh; if (conversationFragment == null) { mainNeedsRefresh = false; - Fragment mainFragment = getFragmentManager().findFragmentById(R.id.main_fragment); + final Fragment mainFragment = fragmentManager.findFragmentById(R.id.main_fragment); if (mainFragment instanceof ConversationFragment) { conversationFragment = (ConversationFragment) mainFragment; } else { conversationFragment = new ConversationFragment(); - FragmentTransaction fragmentTransaction = getFragmentManager().beginTransaction(); + FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction(); fragmentTransaction.replace(R.id.main_fragment, conversationFragment); fragmentTransaction.addToBackStack(null); try { @@ -562,17 +564,18 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio } private void initializeFragments() { - FragmentTransaction transaction = getFragmentManager().beginTransaction(); - Fragment mainFragment = getFragmentManager().findFragmentById(R.id.main_fragment); - Fragment secondaryFragment = getFragmentManager().findFragmentById(R.id.secondary_fragment); + final FragmentManager fragmentManager = getFragmentManager(); + FragmentTransaction transaction = fragmentManager.beginTransaction(); + final Fragment mainFragment = fragmentManager.findFragmentById(R.id.main_fragment); + final Fragment secondaryFragment = fragmentManager.findFragmentById(R.id.secondary_fragment); if (mainFragment != null) { if (binding.secondaryFragment != null) { if (mainFragment instanceof ConversationFragment) { getFragmentManager().popBackStack(); transaction.remove(mainFragment); transaction.commit(); - getFragmentManager().executePendingTransactions(); - transaction = getFragmentManager().beginTransaction(); + fragmentManager.executePendingTransactions(); + transaction = fragmentManager.beginTransaction(); transaction.replace(R.id.secondary_fragment, mainFragment); transaction.replace(R.id.main_fragment, new ConversationsOverviewFragment()); transaction.commit(); @@ -583,7 +586,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio transaction.remove(secondaryFragment); transaction.commit(); getFragmentManager().executePendingTransactions(); - transaction = getFragmentManager().beginTransaction(); + transaction = fragmentManager.beginTransaction(); transaction.replace(R.id.main_fragment, secondaryFragment); transaction.addToBackStack(null); transaction.commit(); From 9526456d75c0a398e36938849b0cdf4ca73da974 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 25 Aug 2021 18:57:20 +0200 Subject: [PATCH 59/82] pulled translations from transifex --- src/main/res/values-de/strings.xml | 8 +++++++- src/main/res/values-gl/strings.xml | 8 +++++++- src/main/res/values-zh-rCN/strings.xml | 8 +++++++- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/src/main/res/values-de/strings.xml b/src/main/res/values-de/strings.xml index 782b5825f..a460ac3e1 100644 --- a/src/main/res/values-de/strings.xml +++ b/src/main/res/values-de/strings.xml @@ -132,6 +132,8 @@ Mit dem Einsenden von Absturzberichten hilfst du bei der Weiterentwicklung Lese- und Empfangsbestätigung senden Informiere deine Kontakte, wenn du eine Nachricht empfangen und gelesen hast + Screenshots verhindern + Ausblenden von App-Inhalten im App-Switcher und Blockieren von Screenshots Benutzeroberfläche OpenKeychain verursachte einen Fehler. Fehlerhafter Schlüssel für die Verschlüsselung. @@ -414,6 +416,7 @@ Audio Video Bild + Vektorgrafik PDF-Dokument Android App Kontakt @@ -912,6 +915,7 @@ Verbindung unterbrochen Anruf zurückgenommen App-Fehler + Verifikationsproblem Auflegen Laufender Anruf Laufender Videoanruf @@ -963,4 +967,6 @@ Keine aktiven Konten unterstützen diese Funktion Das Backup wurde gestartet. Du bekommst eine Benachrichtigung sobald es fertig ist. Video kann nicht aktiviert werden. - + Textdokument + + diff --git a/src/main/res/values-gl/strings.xml b/src/main/res/values-gl/strings.xml index 2baea5f16..75e9c45dd 100644 --- a/src/main/res/values-gl/strings.xml +++ b/src/main/res/values-gl/strings.xml @@ -132,6 +132,8 @@ Ao enviar trazas do sistema estás axudando ao desenvolvemento Confirmación de mensaxes Permitir aos teus contactos saber se recibiches e liches as súas mensaxes + Evitar capturas de pantalla + Agochar os contidos da app no cambiador de apps e bloquear pantallazos Interface OpenKeychain producíu un erro. Chave incorrecta para cifrar. @@ -414,6 +416,7 @@ son video imaxe + gráfico de vector documento PDF App Android Contacto @@ -912,6 +915,7 @@ Perdeuse a conexión Chamada cortada Fallo na aplicación + Problema na verificación Colgar Chamada en curso Videochamada en curso @@ -963,4 +967,6 @@ Ningunha conta activa soporta esta función Comezou a creación da copia de apoio. Recibirás unha notificación cando remate. Non se puido activar o vídeo. - + Documento de texto plano + + diff --git a/src/main/res/values-zh-rCN/strings.xml b/src/main/res/values-zh-rCN/strings.xml index 4df82f547..b3dcf959d 100644 --- a/src/main/res/values-zh-rCN/strings.xml +++ b/src/main/res/values-zh-rCN/strings.xml @@ -129,6 +129,8 @@ 通过发送堆栈跟踪,您可以帮助Conversations持续发展 确认消息 让对方知道你收到并阅读了他们的消息 + 防止截屏 + 在应用切换中隐藏应用程序内容并阻止截图 用户界面 OpenKeychain报告一个错误。 错误的密钥 @@ -411,6 +413,7 @@ 音频 视频 图片 + 矢量图 PDF文档 Android App 联系人 @@ -901,6 +904,7 @@ 连接丢失 通话已撤销 程序错误 + 验证问题 挂断 正在进行的通话 正在进行的视频通话 @@ -950,4 +954,6 @@ 没有活跃帐户支持此功能 已启动备份。一旦完成,你会收到通知。 无法启用视频 - + 纯文本文档 + + From 2819545a43e98cd1224262eb624b4b16a052e9ec Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 25 Aug 2021 20:04:47 +0200 Subject: [PATCH 60/82] click on action bar title should open chat details screen --- .../ui/ConferenceDetailsActivity.java | 8 ++ .../ui/ConversationFragment.java | 10 +-- .../ui/ConversationsActivity.java | 58 ++++++++---- .../conversations/ui/util/ActionBarUtil.java | 88 +++++++++++++++++++ 4 files changed, 139 insertions(+), 25 deletions(-) create mode 100644 src/main/java/eu/siacs/conversations/ui/util/ActionBarUtil.java diff --git a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java index 574035e2c..d35d4808c 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java @@ -1,5 +1,6 @@ package eu.siacs.conversations.ui; +import android.app.Activity; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; @@ -87,6 +88,13 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers } }; + public static void open(final Activity activity, final Conversation conversation) { + Intent intent = new Intent(activity, ConferenceDetailsActivity.class); + intent.setAction(ConferenceDetailsActivity.ACTION_VIEW_MUC); + intent.putExtra("uuid", conversation.getUuid()); + activity.startActivity(intent); + } + private final OnClickListener mNotifyStatusClickListener = new OnClickListener() { @Override public void onClick(View v) { diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index 7bbd4c8e5..93a5ad2bf 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -186,10 +186,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke @Override public void onClick(View v) { - Intent intent = new Intent(getActivity(), ConferenceDetailsActivity.class); - intent.setAction(ConferenceDetailsActivity.ACTION_VIEW_MUC); - intent.putExtra("uuid", conversation.getUuid()); - startActivity(intent); + ConferenceDetailsActivity.open(getActivity(), conversation); } }; private final OnClickListener leaveMuc = new OnClickListener() { @@ -1272,10 +1269,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke activity.switchToContactDetails(conversation.getContact()); break; case R.id.action_muc_details: - Intent intent = new Intent(getActivity(), ConferenceDetailsActivity.class); - intent.setAction(ConferenceDetailsActivity.ACTION_VIEW_MUC); - intent.putExtra("uuid", conversation.getUuid()); - startActivity(intent); + ConferenceDetailsActivity.open(getActivity(), conversation); break; case R.id.action_invite: startActivityForResult(ChooseContactActivity.create(activity, conversation), REQUEST_INVITE_TO_CONVERSATION); diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java index 86f49064a..171eea6ad 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationsActivity.java @@ -30,6 +30,8 @@ package eu.siacs.conversations.ui; +import static eu.siacs.conversations.ui.ConversationFragment.REQUEST_DECRYPT_PGP; + import android.annotation.SuppressLint; import android.app.Activity; import android.app.Fragment; @@ -65,13 +67,16 @@ import eu.siacs.conversations.R; import eu.siacs.conversations.crypto.OmemoSetting; import eu.siacs.conversations.databinding.ActivityConversationsBinding; import eu.siacs.conversations.entities.Account; +import eu.siacs.conversations.entities.Contact; import eu.siacs.conversations.entities.Conversation; +import eu.siacs.conversations.entities.Conversational; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.ui.interfaces.OnBackendConnected; import eu.siacs.conversations.ui.interfaces.OnConversationArchived; import eu.siacs.conversations.ui.interfaces.OnConversationRead; import eu.siacs.conversations.ui.interfaces.OnConversationSelected; import eu.siacs.conversations.ui.interfaces.OnConversationsListItemUpdated; +import eu.siacs.conversations.ui.util.ActionBarUtil; import eu.siacs.conversations.ui.util.ActivityResult; import eu.siacs.conversations.ui.util.ConversationMenuConfigurator; import eu.siacs.conversations.ui.util.MenuDoubleTabUtil; @@ -83,8 +88,6 @@ import eu.siacs.conversations.utils.XmppUri; import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.OnUpdateBlocklist; -import static eu.siacs.conversations.ui.ConversationFragment.REQUEST_DECRYPT_PGP; - public class ConversationsActivity extends XmppActivity implements OnConversationSelected, OnConversationArchived, OnConversationsListItemUpdated, OnConversationRead, XmppConnectionService.OnAccountUpdate, XmppConnectionService.OnConversationUpdate, XmppConnectionService.OnRosterUpdate, OnUpdateBlocklist, XmppConnectionService.OnShowErrorToast, XmppConnectionService.OnAffiliationChanged { public static final String ACTION_VIEW_CONVERSATION = "eu.siacs.conversations.action.VIEW"; @@ -604,18 +607,38 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio private void invalidateActionBarTitle() { final ActionBar actionBar = getSupportActionBar(); - if (actionBar != null) { - Fragment mainFragment = getFragmentManager().findFragmentById(R.id.main_fragment); - if (mainFragment instanceof ConversationFragment) { - final Conversation conversation = ((ConversationFragment) mainFragment).getConversation(); - if (conversation != null) { - actionBar.setTitle(EmojiWrapper.transform(conversation.getName())); - actionBar.setDisplayHomeAsUpEnabled(true); - return; - } + if (actionBar == null) { + return; + } + final FragmentManager fragmentManager = getFragmentManager(); + final Fragment mainFragment = fragmentManager.findFragmentById(R.id.main_fragment); + if (mainFragment instanceof ConversationFragment) { + final Conversation conversation = ((ConversationFragment) mainFragment).getConversation(); + if (conversation != null) { + actionBar.setTitle(EmojiWrapper.transform(conversation.getName())); + actionBar.setDisplayHomeAsUpEnabled(true); + ActionBarUtil.setActionBarOnClickListener( + binding.toolbar, + (v) -> openConversationDetails(conversation) + ); + return; + } + } + actionBar.setTitle(R.string.app_name); + actionBar.setDisplayHomeAsUpEnabled(false); + ActionBarUtil.resetActionBarOnClickListeners(binding.toolbar); + } + + private void openConversationDetails(final Conversation conversation) { + if (conversation.getMode() == Conversational.MODE_MULTI) { + ConferenceDetailsActivity.open(this, conversation); + } else { + final Contact contact = conversation.getContact(); + if (contact.isSelf()) { + switchToAccount(conversation.getAccount()); + } else { + switchToContactDetails(contact); } - actionBar.setTitle(R.string.app_name); - actionBar.setDisplayHomeAsUpEnabled(false); } } @@ -624,17 +647,18 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio if (performRedirectIfNecessary(conversation, false)) { return; } - Fragment mainFragment = getFragmentManager().findFragmentById(R.id.main_fragment); + final FragmentManager fragmentManager = getFragmentManager(); + final Fragment mainFragment = fragmentManager.findFragmentById(R.id.main_fragment); if (mainFragment instanceof ConversationFragment) { try { - getFragmentManager().popBackStack(); - } catch (IllegalStateException e) { + fragmentManager.popBackStack(); + } catch (final IllegalStateException e) { Log.w(Config.LOGTAG, "state loss while popping back state after archiving conversation", e); //this usually means activity is no longer active; meaning on the next open we will run through this again } return; } - Fragment secondaryFragment = getFragmentManager().findFragmentById(R.id.secondary_fragment); + final Fragment secondaryFragment = fragmentManager.findFragmentById(R.id.secondary_fragment); if (secondaryFragment instanceof ConversationFragment) { if (((ConversationFragment) secondaryFragment).getConversation() == conversation) { Conversation suggestion = ConversationsOverviewFragment.getSuggestion(this, conversation); diff --git a/src/main/java/eu/siacs/conversations/ui/util/ActionBarUtil.java b/src/main/java/eu/siacs/conversations/ui/util/ActionBarUtil.java new file mode 100644 index 000000000..80f0ae93e --- /dev/null +++ b/src/main/java/eu/siacs/conversations/ui/util/ActionBarUtil.java @@ -0,0 +1,88 @@ +package eu.siacs.conversations.ui.util; + +import android.content.Context; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.lang.reflect.Field; + +public class ActionBarUtil { + + public static void resetActionBarOnClickListeners(@NonNull View view) { + final View title = findActionBarTitle(view); + final View subtitle = findActionBarSubTitle(view); + if (title != null) { + title.setOnClickListener(null); + } + if (subtitle != null) { + subtitle.setOnClickListener(null); + } + } + + public static void setActionBarOnClickListener(@NonNull View view, + @NonNull final View.OnClickListener onClickListener) { + final View title = findActionBarTitle(view); + final View subtitle = findActionBarSubTitle(view); + if (title != null) { + title.setOnClickListener(onClickListener); + } + if (subtitle != null) { + subtitle.setOnClickListener(onClickListener); + } + } + + private static @Nullable View findActionBarTitle(@NonNull View root) { + return findActionBarItem(root, "action_bar_title", "mTitleTextView"); + } + + private static @Nullable + View findActionBarSubTitle(@NonNull View root) { + return findActionBarItem(root, "action_bar_subtitle", "mSubtitleTextView"); + } + + private static @Nullable View findActionBarItem(@NonNull View root, + @NonNull String resourceName, + @NonNull String toolbarFieldName) { + View result = findViewSupportOrAndroid(root, resourceName); + + if (result == null) { + View actionBar = findViewSupportOrAndroid(root, "action_bar"); + if (actionBar != null) { + result = reflectiveRead(actionBar, toolbarFieldName); + } + } + if (result == null && root.getClass().getName().endsWith("widget.Toolbar")) { + result = reflectiveRead(root, toolbarFieldName); + } + return result; + } + + @SuppressWarnings("ConstantConditions") + private static @Nullable View findViewSupportOrAndroid(@NonNull View root, + @NonNull String resourceName) { + Context context = root.getContext(); + View result = null; + if (result == null) { + int supportID = context.getResources().getIdentifier(resourceName, "id", context.getPackageName()); + result = root.findViewById(supportID); + } + if (result == null) { + int androidID = context.getResources().getIdentifier(resourceName, "id", "android"); + result = root.findViewById(androidID); + } + return result; + } + + @SuppressWarnings("unchecked") + private static T reflectiveRead(@NonNull Object object, @NonNull String fieldName) { + try { + Field field = object.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + return (T) field.get(object); + } catch (final Exception ex) { + return null; + } + } +} From 80d8b6dd880fc51aa928eee4056159e72df796a6 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Fri, 27 Aug 2021 07:47:55 +0000 Subject: [PATCH 61/82] Upload APKs after CI --- .github/workflows/android.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 2c0861ffb..badbbf5d7 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -30,4 +30,9 @@ jobs: run: ./gradlew assembleConversationsFreeCompatDebug - name: Build Conversations (System) run: ./gradlew assembleConversationsFreeSystemDebug + - uses: actions/upload-artifact@v2 + with: + name: Conversations all-flavors (debug) + path: ./build/outputs/apk/**/debug/Conversations-*.apk + From ea0dc558cb823ef0286e44cafb7080ce6de3427d Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 5 Sep 2021 16:29:02 +0200 Subject: [PATCH 62/82] use androidx ExifInterface to parse rotation. fixes #4154 --- build.gradle | 8 +- .../siacs/conversations/entities/Account.java | 4 +- .../persistance/FileBackend.java | 40 +++-- .../siacs/conversations/utils/ExifHelper.java | 161 ------------------ 4 files changed, 35 insertions(+), 178 deletions(-) delete mode 100644 src/main/java/eu/siacs/conversations/utils/ExifHelper.java diff --git a/build.gradle b/build.gradle index 329614436..36bf91f18 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.0.1' + classpath 'com.android.tools.build:gradle:7.0.2' } } @@ -42,12 +42,12 @@ dependencies { } conversationsPlaystoreCompatImplementation("com.android.installreferrer:installreferrer:2.2") conversationsPlaystoreSystemImplementation("com.android.installreferrer:installreferrer:2.2") - quicksyPlaystoreCompatImplementation 'com.google.android.gms:play-services-auth-api-phone:17.5.0' - quicksyPlaystoreSystemImplementation 'com.google.android.gms:play-services-auth-api-phone:17.5.0' + quicksyPlaystoreCompatImplementation 'com.google.android.gms:play-services-auth-api-phone:17.5.1' + quicksyPlaystoreSystemImplementation 'com.google.android.gms:play-services-auth-api-phone:17.5.1' implementation 'org.sufficientlysecure:openpgp-api:10.0' implementation 'com.theartofdev.edmodo:android-image-cropper:2.8.0' implementation 'androidx.appcompat:appcompat:1.2.0' - implementation 'androidx.exifinterface:exifinterface:1.3.2' + implementation 'androidx.exifinterface:exifinterface:1.3.3' implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation 'androidx.emoji:emoji:1.1.0' diff --git a/src/main/java/eu/siacs/conversations/entities/Account.java b/src/main/java/eu/siacs/conversations/entities/Account.java index 0796a1c00..37f8114e8 100644 --- a/src/main/java/eu/siacs/conversations/entities/Account.java +++ b/src/main/java/eu/siacs/conversations/entities/Account.java @@ -5,6 +5,8 @@ import android.database.Cursor; import android.os.SystemClock; import android.util.Log; +import com.google.common.base.Strings; + import org.json.JSONException; import org.json.JSONObject; @@ -247,7 +249,7 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable } public String getHostname() { - return this.hostname == null ? "" : this.hostname; + return Strings.nullToEmpty(this.hostname); } public void setHostname(String hostname) { diff --git a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java index f9aea9da8..74c913162 100644 --- a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java @@ -31,6 +31,7 @@ import android.util.LruCache; import androidx.annotation.RequiresApi; import androidx.annotation.StringRes; import androidx.core.content.FileProvider; +import androidx.exifinterface.media.ExifInterface; import java.io.ByteArrayOutputStream; import java.io.Closeable; @@ -64,7 +65,6 @@ import eu.siacs.conversations.ui.RecordingActivity; import eu.siacs.conversations.ui.util.Attachment; import eu.siacs.conversations.utils.Compatibility; import eu.siacs.conversations.utils.CryptoHelper; -import eu.siacs.conversations.utils.ExifHelper; import eu.siacs.conversations.utils.FileUtils; import eu.siacs.conversations.utils.FileWriterException; import eu.siacs.conversations.utils.MimeUtils; @@ -808,19 +808,34 @@ public class FileBackend { } } - private int getRotation(File file) { - return getRotation(Uri.parse("file://" + file.getAbsolutePath())); + private int getRotation(final File file) { + try (final InputStream inputStream = new FileInputStream(file)) { + return getRotation(inputStream); + } catch (Exception e) { + return 0; + } } - private int getRotation(Uri image) { - InputStream is = null; - try { - is = mXmppConnectionService.getContentResolver().openInputStream(image); - return ExifHelper.getOrientation(is); - } catch (FileNotFoundException e) { + private int getRotation(final Uri image) { + try (final InputStream is = mXmppConnectionService.getContentResolver().openInputStream(image)) { + return is == null ? 0 : getRotation(is); + } catch (final Exception e) { return 0; - } finally { - close(is); + } + } + + private static int getRotation(final InputStream inputStream) throws IOException { + final ExifInterface exif = new ExifInterface(inputStream); + final int orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED); + switch (orientation) { + case ExifInterface.ORIENTATION_ROTATE_180: + return 180; + case ExifInterface.ORIENTATION_ROTATE_90: + return 90; + case ExifInterface.ORIENTATION_ROTATE_270: + return 270; + default: + return 0; } } @@ -1468,7 +1483,8 @@ public class FileBackend { this.resId = resId; } - public @StringRes int getResId() { + public @StringRes + int getResId() { return resId; } } diff --git a/src/main/java/eu/siacs/conversations/utils/ExifHelper.java b/src/main/java/eu/siacs/conversations/utils/ExifHelper.java deleted file mode 100644 index ceda72933..000000000 --- a/src/main/java/eu/siacs/conversations/utils/ExifHelper.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package eu.siacs.conversations.utils; - -import android.util.Log; - -import java.io.IOException; -import java.io.InputStream; - -public class ExifHelper { - private static final String TAG = "CameraExif"; - - public static int getOrientation(InputStream is) { - if (is == null) { - return 0; - } - - byte[] buf = new byte[8]; - int length = 0; - - // ISO/IEC 10918-1:1993(E) - while (read(is, buf, 2) && (buf[0] & 0xFF) == 0xFF) { - int marker = buf[1] & 0xFF; - - // Check if the marker is a padding. - if (marker == 0xFF) { - continue; - } - - // Check if the marker is SOI or TEM. - if (marker == 0xD8 || marker == 0x01) { - continue; - } - // Check if the marker is EOI or SOS. - if (marker == 0xD9 || marker == 0xDA) { - return 0; - } - - // Get the length and check if it is reasonable. - if (!read(is, buf, 2)) { - return 0; - } - length = pack(buf, 0, 2, false); - if (length < 2) { - Log.e(TAG, "Invalid length"); - return 0; - } - length -= 2; - - // Break if the marker is EXIF in APP1. - if (marker == 0xE1 && length >= 6) { - if (!read(is, buf, 6)) return 0; - length -= 6; - if (pack(buf, 0, 4, false) == 0x45786966 && - pack(buf, 4, 2, false) == 0) { - break; - } - } - - // Skip other markers. - try { - is.skip(length); - } catch (IOException ex) { - return 0; - } - length = 0; - } - - // JEITA CP-3451 Exif Version 2.2 - if (length > 8) { - int offset = 0; - byte[] jpeg = new byte[length]; - if (!read(is, jpeg, length)) { - return 0; - } - - // Identify the byte order. - int tag = pack(jpeg, offset, 4, false); - if (tag != 0x49492A00 && tag != 0x4D4D002A) { - Log.e(TAG, "Invalid byte order"); - return 0; - } - boolean littleEndian = (tag == 0x49492A00); - - // Get the offset and check if it is reasonable. - int count = pack(jpeg, offset + 4, 4, littleEndian) + 2; - if (count < 10 || count > length) { - Log.e(TAG, "Invalid offset"); - return 0; - } - offset += count; - length -= count; - - // Get the count and go through all the elements. - count = pack(jpeg, offset - 2, 2, littleEndian); - while (count-- > 0 && length >= 12) { - // Get the tag and check if it is orientation. - tag = pack(jpeg, offset, 2, littleEndian); - if (tag == 0x0112) { - // We do not really care about type and count, do we? - int orientation = pack(jpeg, offset + 8, 2, littleEndian); - switch (orientation) { - case 1: - return 0; - case 3: - return 180; - case 6: - return 90; - case 8: - return 270; - } - Log.i(TAG, "Unsupported orientation"); - return 0; - } - offset += 12; - length -= 12; - } - } - - Log.i(TAG, "Orientation not found"); - return 0; - } - - private static int pack(byte[] bytes, int offset, int length, - boolean littleEndian) { - int step = 1; - if (littleEndian) { - offset += length - 1; - step = -1; - } - - int value = 0; - while (length-- > 0) { - value = (value << 8) | (bytes[offset] & 0xFF); - offset += step; - } - return value; - } - - private static boolean read(InputStream is, byte[] buf, int length) { - try { - return is.read(buf, 0, length) == length; - } catch (IOException ex) { - return false; - } - } -} From 4f362aafac1eb7c5cb976cc1bdac855fcff1c44c Mon Sep 17 00:00:00 2001 From: Alexei Sorokin Date: Fri, 28 May 2021 10:38:28 +0300 Subject: [PATCH 63/82] make the fulltext index for search more space-efficient It now uses the data from the messages table instead of having a copy of each message. The message UUIDs are no longer part of the index. --- .../eu/siacs/conversations/persistance/DatabaseBackend.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index ed6b80c5b..7c0ee3d6d 100644 --- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -170,10 +170,10 @@ public class DatabaseBackend extends SQLiteOpenHelper { private static final String CREATE_MESSAGE_RELATIVE_FILE_PATH_INDEX = "CREATE INDEX message_file_path_index ON " + Message.TABLENAME + "(" + Message.RELATIVE_FILE_PATH + ")"; private static final String CREATE_MESSAGE_TYPE_INDEX = "CREATE INDEX message_type_index ON " + Message.TABLENAME + "(" + Message.TYPE + ")"; - private static final String CREATE_MESSAGE_INDEX_TABLE = "CREATE VIRTUAL TABLE messages_index USING fts4 (uuid TEXT PRIMARY KEY, body TEXT, tokenize = 'unicode61')"; + private static final String CREATE_MESSAGE_INDEX_TABLE = "CREATE VIRTUAL TABLE messages_index USING fts4 (uuid,body,notindexed=\"uuid\",content=\"" + Message.TABLENAME + "\",tokenize='unicode61')"; private static final String CREATE_MESSAGE_INSERT_TRIGGER = "CREATE TRIGGER after_message_insert AFTER INSERT ON " + Message.TABLENAME + " BEGIN INSERT INTO messages_index (uuid,body) VALUES (new.uuid,new.body); END;"; private static final String CREATE_MESSAGE_UPDATE_TRIGGER = "CREATE TRIGGER after_message_update UPDATE of uuid,body ON " + Message.TABLENAME + " BEGIN update messages_index set body=new.body,uuid=new.uuid WHERE uuid=old.uuid; END;"; - private static final String COPY_PREEXISTING_ENTRIES = "INSERT INTO messages_index(uuid,body) SELECT uuid,body FROM " + Message.TABLENAME + ";"; + private static final String COPY_PREEXISTING_ENTRIES = "INSERT INTO messages_index(messages_index) VALUES('rebuild');"; private DatabaseBackend(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); From 754773be553770c059e402e4a07b48cf8d2b066b Mon Sep 17 00:00:00 2001 From: Alexei Sorokin Date: Sat, 12 Jun 2021 20:47:53 +0300 Subject: [PATCH 64/82] match messages from the full-text index by rowid "uuid" is a primary key in "messages" but not in "messages_index", the implication of that is very slow matching by UUID. What can be done instead is matching messages_index.rowid to messages.rowid, that is, an always-present clustered index. This not only improves performance of full-text search but also of just updating messages in any shape or form. --- .../conversations/persistance/DatabaseBackend.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index 7c0ee3d6d..4cdb62214 100644 --- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -171,8 +171,9 @@ public class DatabaseBackend extends SQLiteOpenHelper { private static final String CREATE_MESSAGE_TYPE_INDEX = "CREATE INDEX message_type_index ON " + Message.TABLENAME + "(" + Message.TYPE + ")"; private static final String CREATE_MESSAGE_INDEX_TABLE = "CREATE VIRTUAL TABLE messages_index USING fts4 (uuid,body,notindexed=\"uuid\",content=\"" + Message.TABLENAME + "\",tokenize='unicode61')"; - private static final String CREATE_MESSAGE_INSERT_TRIGGER = "CREATE TRIGGER after_message_insert AFTER INSERT ON " + Message.TABLENAME + " BEGIN INSERT INTO messages_index (uuid,body) VALUES (new.uuid,new.body); END;"; - private static final String CREATE_MESSAGE_UPDATE_TRIGGER = "CREATE TRIGGER after_message_update UPDATE of uuid,body ON " + Message.TABLENAME + " BEGIN update messages_index set body=new.body,uuid=new.uuid WHERE uuid=old.uuid; END;"; + private static final String CREATE_MESSAGE_INSERT_TRIGGER = "CREATE TRIGGER after_message_insert AFTER INSERT ON " + Message.TABLENAME + " BEGIN INSERT INTO messages_index(rowid,uuid,body) VALUES(NEW.rowid,NEW.uuid,NEW.body); END;"; + private static final String CREATE_MESSAGE_UPDATE_TRIGGER = "CREATE TRIGGER after_message_update UPDATE OF uuid,body ON " + Message.TABLENAME + " BEGIN UPDATE messages_index SET body=NEW.body,uuid=NEW.uuid WHERE rowid=OLD.rowid; END;"; + private static final String CREATE_MESSAGE_DELETE_TRIGGER = "CREATE TRIGGER after_message_delete AFTER DELETE ON " + Message.TABLENAME + " BEGIN DELETE FROM messages_index WHERE rowid=OLD.rowid; END;"; private static final String COPY_PREEXISTING_ENTRIES = "INSERT INTO messages_index(messages_index) VALUES('rebuild');"; private DatabaseBackend(Context context) { @@ -262,6 +263,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { db.execSQL(CREATE_MESSAGE_INDEX_TABLE); db.execSQL(CREATE_MESSAGE_INSERT_TRIGGER); db.execSQL(CREATE_MESSAGE_UPDATE_TRIGGER); + db.execSQL(CREATE_MESSAGE_DELETE_TRIGGER); } @Override @@ -522,6 +524,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { db.execSQL(CREATE_MESSAGE_INDEX_TABLE); db.execSQL(CREATE_MESSAGE_INSERT_TRIGGER); db.execSQL(CREATE_MESSAGE_UPDATE_TRIGGER); + db.execSQL(CREATE_MESSAGE_DELETE_TRIGGER); db.execSQL(COPY_PREEXISTING_ENTRIES); } @@ -787,7 +790,9 @@ public class DatabaseBackend extends SQLiteOpenHelper { final SQLiteDatabase db = this.getReadableDatabase(); final StringBuilder SQL = new StringBuilder(); final String[] selectionArgs; - SQL.append("SELECT " + Message.TABLENAME + ".*," + Conversation.TABLENAME + '.' + Conversation.CONTACTJID + ',' + Conversation.TABLENAME + '.' + Conversation.ACCOUNT + ',' + Conversation.TABLENAME + '.' + Conversation.MODE + " FROM " + Message.TABLENAME + " join " + Conversation.TABLENAME + " on " + Message.TABLENAME + '.' + Message.CONVERSATION + '=' + Conversation.TABLENAME + '.' + Conversation.UUID + " join messages_index ON messages_index.uuid=messages.uuid where " + Message.ENCRYPTION + " NOT IN(" + Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE + ',' + Message.ENCRYPTION_PGP + ',' + Message.ENCRYPTION_DECRYPTION_FAILED + ',' + Message.ENCRYPTION_AXOLOTL_FAILED + ") AND " + Message.TYPE + " IN(" + Message.TYPE_TEXT + ',' + Message.TYPE_PRIVATE + ") AND messages_index.body MATCH ?"); + // TODO: change "ON messages_index.uuid=messages.uuid" to + // "ON messages_index.rowid=messages.rowid" when everyone's migrated. + SQL.append("SELECT " + Message.TABLENAME + ".*," + Conversation.TABLENAME + "." + Conversation.CONTACTJID + "," + Conversation.TABLENAME + "." + Conversation.ACCOUNT + "," + Conversation.TABLENAME + "." + Conversation.MODE + " FROM " + Message.TABLENAME + " JOIN " + Conversation.TABLENAME + " ON " + Message.TABLENAME + "." + Message.CONVERSATION + "=" + Conversation.TABLENAME + "." + Conversation.UUID + " JOIN messages_index ON messages_index.uuid=messages.uuid WHERE " + Message.ENCRYPTION + " NOT IN(" + Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE + "," + Message.ENCRYPTION_PGP + "," + Message.ENCRYPTION_DECRYPTION_FAILED + "," + Message.ENCRYPTION_AXOLOTL_FAILED + ") AND " + Message.TYPE + " IN(" + Message.TYPE_TEXT + "," + Message.TYPE_PRIVATE + ") AND messages_index.body MATCH ?"); if (uuid == null) { selectionArgs = new String[]{FtsUtils.toMatchString(term)}; } else { @@ -1039,6 +1044,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { final SQLiteDatabase db = this.getWritableDatabase(); db.beginTransaction(); String[] args = {conversation.getUuid()}; + // TODO: remove once everyone has the after_message_delete trigger db.delete("messages_index", "uuid in (select uuid from messages where conversationUuid=?)", args); int num = db.delete(Message.TABLENAME, Message.CONVERSATION + "=?", args); db.setTransactionSuccessful(); @@ -1049,6 +1055,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { public void expireOldMessages(long timestamp) { final String[] args = {String.valueOf(timestamp)}; SQLiteDatabase db = this.getReadableDatabase(); + // TODO: remove once everyone has the after_message_delete trigger db.beginTransaction(); db.delete("messages_index", "uuid in (select uuid from messages where timeSent Date: Tue, 7 Sep 2021 16:47:40 +0200 Subject: [PATCH 65/82] add database migration for new fts scheme --- .../persistance/DatabaseBackend.java | 62 ++++++++++++------- .../services/XmppConnectionService.java | 5 +- 2 files changed, 42 insertions(+), 25 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java index 4cdb62214..f1cf78760 100644 --- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java @@ -11,6 +11,8 @@ import android.os.SystemClock; import android.util.Base64; import android.util.Log; +import com.google.common.base.Stopwatch; + import org.json.JSONException; import org.json.JSONObject; import org.whispersystems.libsignal.IdentityKey; @@ -62,7 +64,9 @@ import eu.siacs.conversations.xmpp.mam.MamReference; public class DatabaseBackend extends SQLiteOpenHelper { private static final String DATABASE_NAME = "history"; - private static final int DATABASE_VERSION = 48; + private static final int DATABASE_VERSION = 49; + + private static boolean requiresMessageIndexRebuild = false; private static DatabaseBackend instance = null; private static final String CREATE_CONTATCS_STATEMENT = "create table " + Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, " @@ -187,6 +191,17 @@ public class DatabaseBackend extends SQLiteOpenHelper { return values; } + public static boolean requiresMessageIndexRebuild() { + return requiresMessageIndexRebuild; + } + + public void rebuildMessagesIndex() { + final SQLiteDatabase db = getWritableDatabase(); + final Stopwatch stopwatch = Stopwatch.createStarted(); + db.execSQL(COPY_PREEXISTING_ENTRIES); + Log.d(Config.LOGTAG,"rebuilt message index in "+ stopwatch.stop().toString()); + } + public static synchronized DatabaseBackend getInstance(Context context) { if (instance == null) { instance = new DatabaseBackend(context); @@ -520,14 +535,6 @@ public class DatabaseBackend extends SQLiteOpenHelper { db.execSQL(CREATE_RESOLVER_RESULTS_TABLE); } - if (oldVersion < 41 && newVersion >= 41) { - db.execSQL(CREATE_MESSAGE_INDEX_TABLE); - db.execSQL(CREATE_MESSAGE_INSERT_TRIGGER); - db.execSQL(CREATE_MESSAGE_UPDATE_TRIGGER); - db.execSQL(CREATE_MESSAGE_DELETE_TRIGGER); - db.execSQL(COPY_PREEXISTING_ENTRIES); - } - if (oldVersion < 42 && newVersion >= 42) { db.execSQL("DROP TRIGGER IF EXISTS after_message_delete"); } @@ -554,10 +561,10 @@ public class DatabaseBackend extends SQLiteOpenHelper { if (oldVersion < 46 && newVersion >= 46) { final long start = SystemClock.elapsedRealtime(); db.rawQuery("PRAGMA secure_delete = FALSE", null).close(); - db.execSQL("update "+Message.TABLENAME+" set "+Message.EDITED+"=NULL"); + db.execSQL("update " + Message.TABLENAME + " set " + Message.EDITED + "=NULL"); db.rawQuery("PRAGMA secure_delete=ON", null).close(); final long diff = SystemClock.elapsedRealtime() - start; - Log.d(Config.LOGTAG,"deleted old edit information in "+diff+"ms"); + Log.d(Config.LOGTAG, "deleted old edit information in " + diff + "ms"); } if (oldVersion < 47 && newVersion >= 47) { db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN " + Contact.PRESENCE_NAME + " TEXT"); @@ -565,6 +572,19 @@ public class DatabaseBackend extends SQLiteOpenHelper { if (oldVersion < 48 && newVersion >= 48) { db.execSQL("ALTER TABLE " + Contact.TABLENAME + " ADD COLUMN " + Contact.RTP_CAPABILITY + " TEXT"); } + if (oldVersion < 49 && newVersion >= 49) { + db.beginTransaction(); + db.execSQL("DROP TRIGGER IF EXISTS after_message_insert;"); + db.execSQL("DROP TRIGGER IF EXISTS after_message_update;"); + db.execSQL("DROP TABLE IF EXISTS messages_index;"); + db.execSQL(CREATE_MESSAGE_INDEX_TABLE); + db.execSQL(CREATE_MESSAGE_INSERT_TRIGGER); + db.execSQL(CREATE_MESSAGE_UPDATE_TRIGGER); + db.execSQL(CREATE_MESSAGE_DELETE_TRIGGER); + requiresMessageIndexRebuild = true; + db.setTransactionSuccessful(); + db.endTransaction(); + } } private void canonicalizeJids(SQLiteDatabase db) { @@ -779,7 +799,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { try { list.add(0, Message.fromCursor(cursor, conversation)); } catch (Exception e) { - Log.e(Config.LOGTAG,"unable to restore message"); + Log.e(Config.LOGTAG, "unable to restore message"); } } cursor.close(); @@ -790,14 +810,12 @@ public class DatabaseBackend extends SQLiteOpenHelper { final SQLiteDatabase db = this.getReadableDatabase(); final StringBuilder SQL = new StringBuilder(); final String[] selectionArgs; - // TODO: change "ON messages_index.uuid=messages.uuid" to - // "ON messages_index.rowid=messages.rowid" when everyone's migrated. - SQL.append("SELECT " + Message.TABLENAME + ".*," + Conversation.TABLENAME + "." + Conversation.CONTACTJID + "," + Conversation.TABLENAME + "." + Conversation.ACCOUNT + "," + Conversation.TABLENAME + "." + Conversation.MODE + " FROM " + Message.TABLENAME + " JOIN " + Conversation.TABLENAME + " ON " + Message.TABLENAME + "." + Message.CONVERSATION + "=" + Conversation.TABLENAME + "." + Conversation.UUID + " JOIN messages_index ON messages_index.uuid=messages.uuid WHERE " + Message.ENCRYPTION + " NOT IN(" + Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE + "," + Message.ENCRYPTION_PGP + "," + Message.ENCRYPTION_DECRYPTION_FAILED + "," + Message.ENCRYPTION_AXOLOTL_FAILED + ") AND " + Message.TYPE + " IN(" + Message.TYPE_TEXT + "," + Message.TYPE_PRIVATE + ") AND messages_index.body MATCH ?"); + SQL.append("SELECT " + Message.TABLENAME + ".*," + Conversation.TABLENAME + "." + Conversation.CONTACTJID + "," + Conversation.TABLENAME + "." + Conversation.ACCOUNT + "," + Conversation.TABLENAME + "." + Conversation.MODE + " FROM " + Message.TABLENAME + " JOIN " + Conversation.TABLENAME + " ON " + Message.TABLENAME + "." + Message.CONVERSATION + "=" + Conversation.TABLENAME + "." + Conversation.UUID + " JOIN messages_index ON messages_index.rowid=messages.rowid WHERE " + Message.ENCRYPTION + " NOT IN(" + Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE + "," + Message.ENCRYPTION_PGP + "," + Message.ENCRYPTION_DECRYPTION_FAILED + "," + Message.ENCRYPTION_AXOLOTL_FAILED + ") AND " + Message.TYPE + " IN(" + Message.TYPE_TEXT + "," + Message.TYPE_PRIVATE + ") AND messages_index.body MATCH ?"); if (uuid == null) { selectionArgs = new String[]{FtsUtils.toMatchString(term)}; } else { selectionArgs = new String[]{FtsUtils.toMatchString(term), uuid}; - SQL.append(" AND "+Conversation.TABLENAME+'.'+Conversation.UUID+"=?"); + SQL.append(" AND " + Conversation.TABLENAME + '.' + Conversation.UUID + "=?"); } SQL.append(" ORDER BY " + Message.TIME_SENT + " DESC limit " + Config.MAX_SEARCH_RESULTS); Log.d(Config.LOGTAG, "search term: " + FtsUtils.toMatchString(term)); @@ -861,7 +879,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { public List getFilePathInfo() { final SQLiteDatabase db = this.getReadableDatabase(); - final Cursor cursor = db.query(Message.TABLENAME, new String[]{Message.UUID, Message.RELATIVE_FILE_PATH, Message.DELETED}, "type in (1,2,5) and "+Message.RELATIVE_FILE_PATH+" is not null", null, null, null, null); + final Cursor cursor = db.query(Message.TABLENAME, new String[]{Message.UUID, Message.RELATIVE_FILE_PATH, Message.DELETED}, "type in (1,2,5) and " + Message.RELATIVE_FILE_PATH + " is not null", null, null, null, null); final List list = new ArrayList<>(); while (cursor != null && cursor.moveToNext()) { list.add(new FilePathInfo(cursor.getString(0), cursor.getString(1), cursor.getInt(2) > 0)); @@ -874,7 +892,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { public List getRelativeFilePaths(String account, Jid jid, int limit) { SQLiteDatabase db = this.getReadableDatabase(); - final String SQL = "select uuid,relativeFilePath from messages where type in (1,2,5) and deleted=0 and "+Message.RELATIVE_FILE_PATH+" is not null and conversationUuid=(select uuid from conversations where accountUuid=? and (contactJid=? or contactJid like ?)) order by timeSent desc"; + final String SQL = "select uuid,relativeFilePath from messages where type in (1,2,5) and deleted=0 and " + Message.RELATIVE_FILE_PATH + " is not null and conversationUuid=(select uuid from conversations where accountUuid=? and (contactJid=? or contactJid like ?)) order by timeSent desc"; final String[] args = {account, jid.toString(), jid.toString() + "/%"}; Cursor cursor = db.rawQuery(SQL + (limit > 0 ? " limit " + limit : ""), args); List filesPaths = new ArrayList<>(); @@ -899,7 +917,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { public boolean deleted; private FilePathInfo(String uuid, String path, boolean deleted) { - super(uuid,path); + super(uuid, path); this.deleted = deleted; } @@ -1043,9 +1061,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { long start = SystemClock.elapsedRealtime(); final SQLiteDatabase db = this.getWritableDatabase(); db.beginTransaction(); - String[] args = {conversation.getUuid()}; - // TODO: remove once everyone has the after_message_delete trigger - db.delete("messages_index", "uuid in (select uuid from messages where conversationUuid=?)", args); + final String[] args = {conversation.getUuid()}; int num = db.delete(Message.TABLENAME, Message.CONVERSATION + "=?", args); db.setTransactionSuccessful(); db.endTransaction(); @@ -1055,9 +1071,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { public void expireOldMessages(long timestamp) { final String[] args = {String.valueOf(timestamp)}; SQLiteDatabase db = this.getReadableDatabase(); - // TODO: remove once everyone has the after_message_delete trigger db.beginTransaction(); - db.delete("messages_index", "uuid in (select uuid from messages where timeSent { - long deletionDate = getAutomaticMessageDeletionDate(); + if (DatabaseBackend.requiresMessageIndexRebuild()) { + DatabaseBackend.getInstance(this).rebuildMessagesIndex(); + } + final long deletionDate = getAutomaticMessageDeletionDate(); mLastExpiryRun.set(SystemClock.elapsedRealtime()); if (deletionDate > 0) { Log.d(Config.LOGTAG, "deleting messages that are older than " + AbstractGenerator.getTimestamp(deletionDate)); From 38a77dbba64bd74a7ddef27e8bc6ac932b12eff1 Mon Sep 17 00:00:00 2001 From: Maximilian Weiler <16721506+maweil@users.noreply.github.com> Date: Sun, 5 Sep 2021 22:39:29 +0200 Subject: [PATCH 66/82] Fix ImportBackupActivity not covered by screenshot prevention feature --- .../eu/siacs/conversations/ui/ImportBackupActivity.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/conversations/java/eu/siacs/conversations/ui/ImportBackupActivity.java b/src/conversations/java/eu/siacs/conversations/ui/ImportBackupActivity.java index a1f4d8d11..90c214ab4 100644 --- a/src/conversations/java/eu/siacs/conversations/ui/ImportBackupActivity.java +++ b/src/conversations/java/eu/siacs/conversations/ui/ImportBackupActivity.java @@ -29,6 +29,7 @@ import eu.siacs.conversations.databinding.ActivityImportBackupBinding; import eu.siacs.conversations.databinding.DialogEnterPasswordBinding; import eu.siacs.conversations.services.ImportBackupService; import eu.siacs.conversations.ui.adapter.BackupFileAdapter; +import eu.siacs.conversations.ui.util.SettingsUtils; import eu.siacs.conversations.utils.ThemeHelper; public class ImportBackupActivity extends ActionBarActivity implements ServiceConnection, ImportBackupService.OnBackupFilesLoaded, BackupFileAdapter.OnItemClickedListener, ImportBackupService.OnBackupProcessed { @@ -54,6 +55,12 @@ public class ImportBackupActivity extends ActionBarActivity implements ServiceCo this.binding.list.setAdapter(this.backupFileAdapter); this.backupFileAdapter.setOnItemClickedListener(this); } + + @Override + protected void onResume(){ + super.onResume(); + SettingsUtils.applyScreenshotPreventionSetting(this); + } @Override public boolean onCreateOptionsMenu(final Menu menu) { From 96f0a09a5ddfd7684ba10d297329889a1129aa25 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Tue, 7 Sep 2021 16:56:24 +0200 Subject: [PATCH 67/82] pulled translations from transifex --- src/main/res/values-de/strings.xml | 18 +-- src/main/res/values-gl/strings.xml | 6 +- src/main/res/values-hu/strings.xml | 16 +++ src/main/res/values-it/strings.xml | 7 +- src/main/res/values-ja/strings.xml | 162 +++++++++++++------------ src/main/res/values-pl/strings.xml | 8 +- src/main/res/values-pt-rBR/strings.xml | 8 +- 7 files changed, 132 insertions(+), 93 deletions(-) diff --git a/src/main/res/values-de/strings.xml b/src/main/res/values-de/strings.xml index a460ac3e1..c5a7e680b 100644 --- a/src/main/res/values-de/strings.xml +++ b/src/main/res/values-de/strings.xml @@ -94,7 +94,7 @@ Gerät auswählen Unverschlüsselt schreiben… Nachricht senden - Sende Nachricht an %s + Nachricht an %s senden OMEMO-verschlüsselt schreiben… v\\OMEMO-verschlüsselte Nachricht senden OpenPGP-verschlüsselt schreiben… @@ -112,7 +112,7 @@ Deine Nachricht konnte nicht verschlüsselt werden, weil dein Kontakt seinen öffentlichen Schlüssel nicht bekannt gibt.\n\nBitte sage deinem Kontakt, er möge OpenPGP einrichten. Keine OpenPGP-Schlüssel gefunden Deine Nachrichten konnten nicht verschlüsselt werden, weil deine Kontakte ihre öffentlichen Schlüssel nicht bekannt geben.\n\nBitte sage ihnen, sie mögen OpenPGP einrichten. - Allgemeines + Allgemein Dateien annehmen Dateien automatisch annehmen, die kleiner sind als … Anhänge @@ -151,7 +151,7 @@ Bilddatei konnte nicht konvertiert werden Datei nicht gefunden Allgemeiner Fehler. Vielleicht hast du keinen Speicherplatz mehr? - Die App, mit der du das Bild ausgesucht hast, hat keine Rechte eingeräumt, um das Bild zu betrachten.\n\nBenutze einen anderen Dateimanager, um ein Bild auszuwählen. + Die App, mit der du das Bild ausgesucht hast, hat nicht die erforderlichen Berechtigungen, um das Bild zu betrachten.\n\nBenutze einen anderen Dateimanager, um ein Bild auszuwählen. Die App, die du zum Teilen dieser Datei verwendet hast, hat nicht die erforderlichen Berechtigungen bereitgestellt. Unbekannt Vorübergehend abgeschaltet @@ -170,7 +170,7 @@ Domain nicht überprüfbar Verstoß gegen die Richtlinien Inkompatibler Server - Stream Fehler + Stream-Fehler Fehler beim Öffnen des Streams Unverschlüsselt OTR @@ -182,7 +182,7 @@ Öffentlichen OpenPGP-Schlüssel veröffentlichen Öffentlichen OpenPGP-Schlüssel verwerfen Bist du sicher, dass du deinen öffentlichen OpenPGP-Schlüssel aus deiner Anwesenheitsmitteilung entfernen möchtest?\nDeine Kontakte können dir dann keine OpenPGP-verschlüsselten Nachrichten senden. - Öffentlicher OpenPGP-Schlüssel veröffentlicht + Öffentlicher OpenPGP-Schlüssel veröffentlicht. Konto aktivieren Bist du dir sicher? Die Löschung deines Kontos löscht deinen gesamten Gesprächsverlauf @@ -252,14 +252,14 @@ Thema Gruppenchat wird beigetreten… Verlassen - Der Kontakt hat dich zur Kontaktliste hinzugefügt + Kontakt hat dich zur Kontaktliste hinzugefügt Auch hinzufügen %s hat bis zu diesem Punkt gelesen %s haben bis zu diesem Punkt gelesen %1$s +%2$d andere haben bis zu diesem Punkt gelesen Alle haben bis zu diesem Punkt gelesen Veröffentlichen - Avatar antippen, um ein Bild aus Galerie auszuwählen + Avatar antippen, um ein Bild aus der Galerie auszuwählen Veröffentliche… Der Server hat die Veröffentlichung des Avatars abgelehnt. Bild konnte nicht konvertiert werden @@ -306,7 +306,7 @@ verwende Konto %s gehostet bei %s %s auf HTTP-Host wird überprüft - Nicht verbunden, bitte später versuchen + Du bist nicht verbunden. Bitte versuche es später noch einmal %s-Größe prüfen %1$s-Größe auf %2$s prüfen Nachrichtenoptionen @@ -792,7 +792,7 @@ Überprüfe %s %s geschickt.]]> Wir haben dir eine weitere SMS mit einem 6-stelligen Code geschickt. - Gib bitte den 6-stellige PIN unten ein. + Gib bitte die 6-stellige PIN unten ein. SMS erneut versenden SMS erneut versenden (%s) Bitte warten (%s) diff --git a/src/main/res/values-gl/strings.xml b/src/main/res/values-gl/strings.xml index 75e9c45dd..808a00ca0 100644 --- a/src/main/res/values-gl/strings.xml +++ b/src/main/res/values-gl/strings.xml @@ -131,7 +131,7 @@ Nunca enviar informe de erros Ao enviar trazas do sistema estás axudando ao desenvolvemento Confirmación de mensaxes - Permitir aos teus contactos saber se recibiches e liches as súas mensaxes + Permitir aos teus contactos saber se recibiches e leches as súas mensaxes Evitar capturas de pantalla Agochar os contidos da app no cambiador de apps e bloquear pantallazos Interface @@ -151,7 +151,7 @@ Non se puido converter o ficheiro de imaxe Arquivo non atopado Erro xeral de I/O. ¿Quedaches sen espazo no disco? - A app utilizada para escoller esta imaxe non deu permisos suficientes para ler o ficheiro.\n\nUsa un xestor de ficheiros diferente para escoller a imaxe + A app utilizada para seleccionar esta imaxe non deu permisos suficientes para ler o ficheiro.\n\nUsa un xestor de ficheiros diferente para elexir a imaxe A app que usaches para compartir este ficheiro non concedeu os permisos suficientes. Descoñecido Desactivado temporalmente @@ -624,7 +624,7 @@ Vas verificar as chaves OMEMO de %1$s despois de premer na ligazón. Esto só é seguro se seguiches esta ligazón desde unha fonte de confianza onde só %2$s a podería ter publicado. Validar chaves OMEMO Mostrar inactivos - Amagar inactivos + Agochar inactivos Retirar confianza a dispositivo Tes a certeza de que queres eliminar a verificación deste dispositivo?\nEste dispositivo e as súas mensaxes serán marcados como \"Non confiable\". diff --git a/src/main/res/values-hu/strings.xml b/src/main/res/values-hu/strings.xml index 7ef8338a6..162212928 100644 --- a/src/main/res/values-hu/strings.xml +++ b/src/main/res/values-hu/strings.xml @@ -53,6 +53,7 @@ %s összes partnerének tiltását feloldja? Partner tiltva Tiltva + Szeretné eltávolítani ezt a könyvjelzőkből: %s? Ezzel a könyvjelzővel megjelölt beszélgetései nem lesznek eltávolítva. Új fiók regisztrálása a kiszolgálón Jelszó megváltoztatása a kiszolgálón Megosztás ezzel… @@ -70,6 +71,8 @@ Tiltás feloldása Mentés Rendben + %1$s összeomlott + Azzal, hogy az XMPP fiókja használatával beküldi a veremkiíratásokat, elősegítheti a %1$s alkalmazás folyamatos fejlesztését. Küldés most Sose kérdezzen újra Nem sikerült kapcsolódni a fiókhoz @@ -99,6 +102,7 @@ Küldés titkosítatlanul A visszafejtés sikertelen. Talán nem rendelkezik a megfelelő személyes kulccsal. OpenKeychain + OpenKeychain alkalmazást használja az üzenetek titkosításához és visszafejtéséhez, valamint a személyes kulcsai kezeléséhez.

Ez GPLv3+ szerint licencelt, és elérhető az F-Droid és a Google Play szoftveráruházakban.

(Ezután indítsa újra a %1$s alkalmazást.)]]>
Újraindítás Telepítés Telepítse az OpenKeychain alkalmazás @@ -120,6 +124,7 @@ Csengőhang Értesítési hang Értesítési hang új üzeneteknél + Csengőhang bejövő hívásnál Türelmi idő Az időtartam, amíg az értesítések némítva vannak az egyéb eszközei egyikén történt tevékenység észlelése után. Speciális @@ -127,7 +132,10 @@ A veremkiíratások elküldésével segíti a fejlesztést Üzenetek megerősítése Tudassa a partnereivel, hogy megkapta és elolvasta az üzeneteiket + Képernyőfotó készítésének megakadályozása + Az alkalmazás tartalmának elrejtése az alkalmazásváltóban és a képernyőfotók blokkolása Felhasználói felület + Az OpenKeychain hibát produkált. Rossz kulcs a titkosításhoz. Elfogadás Hiba történt @@ -143,6 +151,8 @@ Nem sikerült átalakítani a képet A fájl nem található Általános bemeneti/kimeneti hiba. Talán elfogyott a tárolóhely? + A kép kiválasztásához használt alkalmazás nem biztosított számunkra elegendő jogosultságot a fájl olvasásához.\n\nHasználjon másik fájlkezelőt a kép kiválasztásához + A fájl megosztásához használt alkalmazás nem biztosított számunkra elegendő jogosultságot. Ismeretlen Átmenetileg letiltva Elérhető @@ -157,6 +167,7 @@ A kiszolgáló nem támogatja a regisztrációt Érvénytelen regisztrációs token A TLS-egyeztetés sikertelen + Tartomány nem ellenőrizhető Irányelv megsértése Nem kompatibilis kiszolgáló Adatfolyamhiba @@ -174,6 +185,7 @@ Az OpenPGP nyilvános kulcs közzé lett téve. Fiók engedélyezése Biztos benne? + A fiók törlésével az összes beszélgetési előzményei is eltávolításra kerülnek Hang rögzítése XMPP-cím XMPP-cím tiltása @@ -204,6 +216,7 @@ egy napja volt aktív %d napja volt aktív Titkosított üzenet. Telepítse az OpenKeychain alkalmazást a visszafejtéshez. + Új OpenPGP titkosítású üzenetek találhatók OpenPGP kulcsazonosító OMEMO ujjlenyomat v\\OMEMO ujjlenyomat @@ -394,6 +407,7 @@ hang videó kép + vektorgrafika PDF-dokumentum Android alkalmazás Partner @@ -447,6 +461,8 @@ A kiszolgáló nem felelős a tartományért Törött Elérhetőség + Távol, ha az eszköz le van zárva + Mutasson „Távoli”-ként, ha az eszköz le van zárva Rezgés kezelése csendes módként Kiterjesztett kapcsolati beállítások Gépnév és port beállításainak megjelenítése egy fiók beállításakor diff --git a/src/main/res/values-it/strings.xml b/src/main/res/values-it/strings.xml index 9271b113d..a4544d798 100644 --- a/src/main/res/values-it/strings.xml +++ b/src/main/res/values-it/strings.xml @@ -132,6 +132,8 @@ Se scegli di inviare una segnalazione dell’errore aiuterai lo sviluppo Conferma messaggi Fai sapere ai tuoi contatti quando hai ricevuto e letto i loro messaggi + Impedisci cattura schermo + Nascondi i contenuti dell\'app nell\'elenco recenti e blocca la cattura delle schermate Interfaccia utente OpenKeychain ha generato un errore. Chiave di cifratura non valida. @@ -912,6 +914,7 @@ Connessione persa Chiamata ritirata Errore dell\'app + Problema di verifica Riaggancia Chiamata in corso Chiamata video in corso @@ -963,4 +966,6 @@ Nessun account attivo supporta questa funzione Il backup è iniziato. Riceverai una notifica una volta completato. Impossibile attivare il video. - + Documento di testo + + diff --git a/src/main/res/values-ja/strings.xml b/src/main/res/values-ja/strings.xml index f1a3daa2f..1dde2ca62 100644 --- a/src/main/res/values-ja/strings.xml +++ b/src/main/res/values-ja/strings.xml @@ -71,23 +71,23 @@ %1$s がクラッシュしました あなたの XMPP アカウントを使用してスタックトレースの送信をすることで、 %1$s の継続的な開発を支援します。 今すぐ送信 - 今後表示しない + 今後は表示しない アカウントに接続できません 複数のアカウントに接続できません タップしてアカウントを管理 - 添付ファイル - 連絡先が連絡先名簿にありません。追加しますか? + ファイルを添付 + 連絡先が連絡先名簿にありません。名簿に追加しますか? 連絡先を追加 配信に失敗しました - 転送用画像の準備中 - 転送用画像の準備中 + 送信用画像の準備中 + 送信用画像の準備中 ファイル共有中。しばらくお待ちください… - 履歴をクリア - 会話履歴をクリア + 履歴を消去 + 会話履歴を消去 この会話のすべてのメッセージを削除してもよろしいですか?\n\n警告: 他のデバイスやサーバーに保存されているメッセージのコピーには影響しません。 ファイルを削除 このファイルを削除してもよろしいですか?\n\n警告: これは、他のデバイスやサーバーに保存されているファイルのコピーは削除しません。 - その後、この会話を閉じる + この後、この会話を閉じる デバイスを選択 暗号化されていないメッセージを送信 メッセージを送信 @@ -96,8 +96,8 @@ v\\OMEMO 暗号化メッセージを送信 OpenPGP 暗号化メッセージを送信 ニックネームが変更されました - 暗号化されていない送信 - 復号に失敗しました。おそらく秘密鍵が正しくないようです。 + 暗号化せずに送信 + 復号に失敗しました。適切な秘密鍵を持っていないのかもしれません。 OpenKeychain OpenKeychain を利用して、メッセージの暗号化および復号、そしてあなたの公開鍵を管理します。

それは GPLv3+ ライセンスの下で、F-Droid および Google Play から利用可能です。

(後で %1$s を再起動してください。)]]>
再起動 @@ -126,9 +126,11 @@ 別のデバイスでの操作を検知した際に、通知を止める時間の長さ 詳細 クラッシュレポートを送信しない - スタックトレースを送信して、Conversations の継続的な開発を支援します - メッセージの確認 + スタックトレースを送信すると、 Conversations の開発を支援します + メッセージを確認 あなたがメッセージを受信して読んだときに、連絡先に知らせます + スクリーンショットを防ぐ + アプリスイッチャーでアプリの内容を隠し、スクリーンショットを防ぐ UI OpenKeychain でエラーが発生しました。 暗号化の鍵が不正です。 @@ -139,15 +141,15 @@ 参加アップデートを送信 参加アップデートを受信 参加アップデートを問合せ - 写真を選択 + 画像を選択 写真を撮影 事前にサブスクリプション要求を許可する 選択したファイルは画像ではありません 画像ファイルを変換できません ファイルが見つかりません - 一般的な I/O エラー。おそらく空き容量がなくなっていませんか? - あなたが画像の選択のために使用したアプリは、読み取りに必要なアクセス権がありません。\n\n別のファイルマネージャを使用して、画像を選択してください。 - このファイルを共有するために使用したアプリに、十分な許可が与えられていませんでした。 + 一般的な入出力エラー。空き容量がなくなっていませんか? + あなたが画像の選択のために使用したアプリは、読み取りに必要なアクセス権がありません。\n\n画像を選択するために、別のファイルマネージャーを使ってください + このファイルを共有するために使用したアプリは、十分な許可が与えられていませんでした。 不明 一時的に無効 オンライン @@ -155,11 +157,11 @@ オフライン 許可されていません サーバーが見つかりません - 接続エラー + 接続なし 登録に失敗しました ユーザー名は既に使用されています 登録が完了しました - サーバーが登録をサポートしていません + サーバーは登録をサポートしていません トークンが無効です TLS ネゴシエーションに失敗しました 検証不可能なドメイン @@ -176,7 +178,7 @@ アバターを公開 OpenPGP 公開鍵を公開 OpenPGP 公開鍵を削除 - 在席告知から OpenPGP 公開鍵を削除してもよろしいですか?\n連絡先はあなたに OpenPGP 暗号化メッセージを送信できなくなります。 + 存在告知から OpenPGP 公開鍵を削除してもよろしいですか?\n連絡先はあなたに OpenPGP 暗号化メッセージを送信できなくなります。 OpenPGP 公開鍵を公開しました。 アカウントを有効にする よろしいですか? @@ -190,11 +192,11 @@ メモリ不足です。画像が大きすぎます %s をお使いのアドレス帳に追加しますか? サーバー情報 - XEP-0313: メッセージ アーカイブ管理 + XEP-0313: メッセージ中央管理 XEP-0280: メッセージ複写 XEP-0352: クライアント状態表示 XEP-0191: ブロッキング コマンド - XEP-0237: 名簿バージョニング + XEP-0237: 名簿バージョン管理 XEP-0198: ストリーム管理 XEP-0215: 外部サービスの発見 XEP-0163: PEP (アバター / OMEMO) @@ -211,7 +213,7 @@ 1日前に会いました %d日前に会いました 暗号化されたメッセージです。復号するには OpenKeychain をインストールしてください。 - 新しい OpenPGP 暗号化されたメッセージが見つかりました + 新しい OpenPGP で暗号化されたメッセージが見つかりました OpenPGP 鍵 ID OMEMO フィンガープリント v\\OMEMO フィンガープリント @@ -233,7 +235,7 @@ 参加 channel@conference.example.com/nick channel@conference.example.com - ブックマークとして保存 + ブックマークに保存 ブックマークを削除 グループチャットを破棄する 談話室を破棄する @@ -254,14 +256,14 @@ 公開 アバターをタップしてギャラリーから画像を選択します 公開中… - サーバーがあなたの公開を拒否しました + サーバーはあなたが公開するものを拒否しました 画像を変換できません - ディスクにアバターを保存できませんでした + ディスクにアバターを保存できません (または長押しするとデフォルトに戻します) ご利用のサーバーは、アバターの公開をサポートしていません ささやいた %s へ - 非公開メッセージを %s に送信 + 非公開メッセージを %s へ送信 接続 このアカウントは既に存在します 次へ @@ -282,15 +284,15 @@ ご利用には注意してください %s について 消音時間 - 開始時間 - 終了時間 + 開始時刻 + 終了時刻 消音時間を有効にする 消音時間の間、通知は無音になります その他 ブックマークと同期 ブックマークに従って、グループチャットに自動で参加します。 OMEMO フィンガープリントをクリップボードにコピーしました - このグループチャットから追い出されています + このグループチャットから出禁にされています このグループチャットはメンバー制です リソース制約 このグループチャットから蹴り出されています @@ -298,7 +300,7 @@ 既にこのグループチャットに参加していません アカウント %s を使用 %s 上でホストされた - HTTP ホストの %s を確認中 + HTTP ホスト上の %s を確認中 接続されていません。後でもう一度お試しください %s の大きさを確認 %2$s で %1$s の大きさを確認 @@ -307,7 +309,7 @@ 引用として貼り付け 元の URL をコピー 再送 - ファイル URL + ファイルの URL URL をクリップボードにコピーしました XMPP アドレスをクリップボードにコピーしました エラーメッセージをクリップボードにコピーしました @@ -350,16 +352,16 @@ グループチャットのサーバーが見つかりませんでした グループチャットを作成できません アカウントのアバター - OMEMO フィンガープリントをクリップボードにコピー + クリップボードに OMEMO フィンガープリントをコピー OMEMO 鍵を再生成 - デバイスをクリア - OMEMO の告知から他のすべてのデバイスをクリアしてもよろしいですか? お使いのデバイスが次回接続したとき、それらは自分自身を再告知しますが、その間に送信されたメッセージを受信できない場合があります。 + デバイスを消去 + OMEMO の告知から他のすべてのデバイスを消去してもよろしいですか? お使いのデバイスが次回接続したとき、それらのデバイスは自分自身を再告知しますが、その間に送信されたメッセージを受信できない場合があります。 この連絡先で使用可能な鍵がありません。\nサーバーから新しい鍵を取得できませんでした。連絡先のサーバーに問題がある可能性があります。 - この連絡先で利用可能な鍵はありません。\n双方に存在サブスクリプションあることを確認してください。 - 何か問題が発生しました。 + この連絡先で利用可能な鍵はありません。\n双方に存在サブスクリプションがあることを確認してください。 + 何か問題が発生しました サーバーから履歴を取得中 - サーバーにこれ以上履歴はありません - アップデート中… + サーバーにこれ以上履歴がありません + 更新中… パスワードを変更しました! パスワードを変更できません パスワードを変更 @@ -383,10 +385,10 @@ グループチャットから削除 談話室から削除 %s の所属を変更できません - グループチャットから追い出す - 談話室から追い出す - あなたは公開談話室から %s を削除しようとしています。その唯一の手段は、そのユーザーを永久に追い出すことです。 - 今すぐ追い出す + グループチャットから出禁にする + 談話室から出禁にする + あなたは公開談話室から %s を削除しようとしています。その唯一の手段は、そのユーザーを永久に出禁にすることです。 + 今すぐ出禁にする %s の役割を変更できません 非公開グループチャットの環境設定 公開談話室の環境設定 @@ -397,18 +399,19 @@ グループチャットのオプションが変更されました! グループチャットのオプションを変更できませんでした なし - 通知があるまで + 通知が来るまで スヌーズ 返信する 既読にする 入力 - Enter は送信 - メッセージの送信に Enter キーを使用する。このオプションが無効でも、常に Ctrl+Enter でメッセージを送信できます。 + Enter で送信 + メッセージの送信に Enter キーを使用します。このオプションが無効でも、常に Ctrl+Enter でメッセージを送信できます。 Enter キーを表示 絵文字キーを Enter キーに変更 音声 ビデオ 画像 + ベクター画像 PDF 文書 Android アプリ 連絡先 @@ -432,15 +435,15 @@ システムの CA を信頼しない すべての証明書を手動で承認する必要があります 証明書を削除 - 手動で承認した証明書を削除します - 手動で承認した証明書はありません + 手動で承認した証明書を削除 + 手動で承認した証明書がありません 証明書を削除 - 選択を削除 - キャンセル + 選択したものを削除 + 中止 - %d 証明書を削除しました + %d個の証明書を削除しました - “送信”ボタンをクイックアクションで置き換えます + “送信”ボタンをクイックアクションで置き換える クイックアクション なし 最近使用した @@ -458,24 +461,24 @@ ダウンロードに失敗しました: ファイルに書き込みできません Tor ネットワークが利用できません バインド失敗 - サーバーがこのドメインに応答しません + そのサーバーはこのドメインに責任を持ちません 壊れています 在席状況 - デバイスがロックされたときは離席 - デバイスがロックされたときは離席と表示 - サイレントモード時は取込中 - デバイスがサイレントモードの時は取込中と表示 + デバイスがロックされているときは離席 + デバイスがロックされているときは離席と表示 + サイレントモードのときは取込中 + デバイスがサイレントモードのときは取込中と表示 バイブレートをサイレントモードとして扱う - デバイスがバイブレート時は取込中と表示 + デバイスがバイブレートのときは取込中と表示 拡張接続設定 アカウントを設定するときにホスト名とポートの設定を表示します xmpp.example.com 証明書でログイン 証明書を解析できません - アーカイブの設定 - サーバー側のアーカイブの設定 - アーカイブの設定を取得しています。しばらくお待ちください… - アーカイブの設定を取得できません + アーカイブ設定 + サーバー側のアーカイブ設定 + アーカイブ設定を取得しています。しばらくお待ちください… + アーカイブ設定を取得できません キャプチャが要求されました 上の画像からテキストを入力してください 信頼されていない証明書チェーン @@ -490,11 +493,11 @@ ホスト名 ポート サーバーまたは .onion のアドレス - これは有効なポート番号ではありません - これは有効なホスト名ではありません - %1$d / %2$d アカウントが接続しました + 有効なポート番号ではありません + 有効なホスト名ではありません + %2$d個中%1$d個のアカウントが接続しました - %d メッセージ + %d件のメッセージ さらにメッセージを読み込む %s でファイル共有 @@ -515,7 +518,7 @@ 常に 大きい画像のみ 電池最適化が有効 - お使いのデバイスは、%1$s で通知の遅延やメッセージの損失につながる可能性のある、重い電池の最適化を使用しています。\nそれらを無効にすることをお勧めします。 + お使いのデバイスは、%1$s で通知の遅延やメッセージの損失につながる可能性のある、重い電池の最適化を使用しています。\nそれを無効にすることをお勧めします。 お使いのデバイスは、%1$s で通知の遅延やメッセージの損失につながる可能性のある、重い電池の最適化を使用しています。\n\n今、それらを無効にするように求められます。 無効 選択した範囲が大きすぎます @@ -588,19 +591,19 @@ エラーメッセージを表示 エラーメッセージ データセーバーを有効にしました - お使いのオペレーティングシステムは、%1$s がバックグラウンドのときにインターネットにアクセスすることを制限しています。新しいメッセージの通知を受信するには、“データセーバー”がオンになっているとき、%1$s に無制限のアクセスを許可する必要があります。\n%1$s は可能なときにデータを保存するための努力をします。 + お使いのオペレーティングシステムは、%1$s がバックグラウンドのときにインターネットにアクセスすることを制限しています。新しいメッセージの通知を受信するには、“データセーバー”がオンならば、%1$s に無制限のアクセスを許可する必要があります。\n%1$s は可能なときにデータを保存するための努力をします。 お使いのデバイスは、%1$s のデータセーバーを無効にできません。 一時ファイルを作成できません このデバイスは検証済です フィンガープリントをコピー - 所有するすべての OMEMO 鍵を確認完了 + 所有するすべての OMEMO 鍵を検証完了 バーコードに、この会話のフィンガープリントが含まれていません。 フィンガープリントを検証しました カメラを使用して連絡先のバーコードをスキャンします 鍵が取得されるのをお待ちください - バーコードとして共有 - XMPP URI として共有 - HTTP リンクとして共有 + バーコードで共有 + XMPP URI で共有 + HTTP リンクで共有 検証前に白紙信託する 認証されていない連絡先からの新規デバイスを信頼するが、認証されている連絡先からの新規デバイスについては手動での確認を求める。 OMEMO 鍵を盲目的に信用していた。つまり、他の人かもしれないし、誰かが盗聴しているかもしれない。 @@ -704,10 +707,10 @@ このデバイス向けにメッセージは暗号化されませんでした。 OMEMO メッセージの復号に失敗しました。 元に戻す - 場所の共有が無効 + 位置の共有が無効 位置を固定 位置を固定しない - 場所をコピー + 位置をコピー 位置を共有 位置を共有 位置を表示 @@ -718,8 +721,8 @@ メッセージを検索 GIF 会話を表示 - 場所共有プラグイン - 場所共有プラグインの代わりに、組み込みの地図を使う + 位置共有プラグイン + 位置共有プラグインの代わりに、組み込みの地図を使う ウェブアドレスをコピー XMPP アドレスをコピー S3 の HTTP ファイル共有 @@ -735,7 +738,7 @@ グループチャット名 このグループチャットは破棄されました フォアグラウンドサービス - この通知カテゴリーは %1$s が実行されていることを表示する、永続的な通知を表示するために使用されます。 + この通知カテゴリーは %1$s が実行していることを表示する、永続的な通知を表示するために使用されます。 ステータス情報 接続の問題 この通知カテゴリーは、アカウントへの接続に問題があった場合に、通知を表示するために使用されます。 @@ -744,6 +747,7 @@ メッセージ 着信 発信 + サイレントメッセージ この通知グループは、音を鳴らしてはいけない通知を表示するために使用します。例えば、他のデバイスでアクティブになっているときなどです (猶予期間)。 配信に失敗 メッセージ通知設定 @@ -813,7 +817,7 @@ 電子書籍 原物 (非圧縮) …で開く - Conversations プロフィール写真 + Conversations プロフィール画像 アカウントを選択 バックアップを復元 復元 @@ -940,4 +944,6 @@ この機能をサポートするアクティブなアカウントがありません バックアップを開始しました。 バックアップが完了すると通知が届きます。 映像を有効化できません。 - + プレーンテキスト文書 + + diff --git a/src/main/res/values-pl/strings.xml b/src/main/res/values-pl/strings.xml index eef1ba92b..7325816ed 100644 --- a/src/main/res/values-pl/strings.xml +++ b/src/main/res/values-pl/strings.xml @@ -138,6 +138,8 @@ Wysyłając nam ślady stosu pomagasz w rozwoju Potwierdzenia wiadomości Zezwól na wysyłanie do osób z twojej listy kontaktów informacji o tym, kiedy otrzymałeś i przeczytałeś wiadomość od nich + Zapobiegaj zrzutom ekranu + Ukryj zawartość aplikacji w podglądzie aplikacji oraz zablokuj zrzuty ekranu UI OpenKeychain zgłosiło błąd. Zły klucz szyfrowania. @@ -420,6 +422,7 @@ plik audio plik wideo obraz + grafika wektorowa Dokument PDF Aplikacja Androida Kontakt @@ -935,6 +938,7 @@ Administrator twojego serwera będzie mógł czytać twoje wiadomości, ale moż Utracono połączenie Anulowane połączenie Błąd aplikacji + Problem z weryfikacją Rozłącz Połączenie wychodzące Wideorozmowa wychodząca @@ -990,4 +994,6 @@ Administrator twojego serwera będzie mógł czytać twoje wiadomości, ale moż Nie ma aktywnych kont wspierających tę funkcję Tworzenie kopii zapasowej się rozpoczęło. Dostaniesz powiadomienie kiedy się zakończy. Nie można włączyć wideo. - + Dokument zwykłego tekstu + + diff --git a/src/main/res/values-pt-rBR/strings.xml b/src/main/res/values-pt-rBR/strings.xml index ffc3c0682..5e15e47c1 100644 --- a/src/main/res/values-pt-rBR/strings.xml +++ b/src/main/res/values-pt-rBR/strings.xml @@ -132,6 +132,8 @@ Ao enviar os stack traces você está colaborando com o desenvolvimento Confirmação de mensagens Permite que seus contatos saibam quando você recebeu e leu as mensagens deles. + Impedir capturas de tela + Esconde o conteúdo do app no alternador de apps e bloqueia capturas de tela IU O OpenKeychain produziu um erro. Chave ruim para a criptografia @@ -414,6 +416,7 @@ áudio vídeo imagem + gráfico vetorial Documento PDF Aplicativo Android Contato @@ -912,6 +915,7 @@ Conexão perdida Chamada rejeitada Falha no aplicativo + Problema de verificação Desligar Chamada em andamento Chamada de vídeo em andamento @@ -963,4 +967,6 @@ Nenhuma conta ativa suporta esse recurso O backup foi iniciado. Você receberá uma notificação assim que ele for concluído. Não foi possível habilitar o vídeo. - + Documento em texto puro + + From 8d45cc5827f9fb3f6535749cc7d5c624fc08f5b5 Mon Sep 17 00:00:00 2001 From: Millesimus Date: Sat, 4 Sep 2021 15:56:37 +0200 Subject: [PATCH 68/82] Fixing trailing characters treated as part of URI error (#3938). --- .../java/eu/siacs/conversations/utils/Patterns.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/utils/Patterns.java b/src/main/java/eu/siacs/conversations/utils/Patterns.java index fae13aaea..d220b3326 100644 --- a/src/main/java/eu/siacs/conversations/utils/Patterns.java +++ b/src/main/java/eu/siacs/conversations/utils/Patterns.java @@ -304,9 +304,15 @@ public class Patterns { + "\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,64}(?:\\:(?:[a-zA-Z0-9\\$\\-\\_" + "\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,25})?\\@"; private static final String PORT_NUMBER = "\\:\\d{1,5}"; + private static final String PATH_AND_QUERY_CHARS_WITHOUT_SLASH = + "\\;\\?\\:\\@\\&\\=\\#\\~" // plus optional query params + + "\\-\\.\\+\\!\\*\\'\\(\\)\\,\\_"; private static final String PATH_AND_QUERY = "\\/(?:(?:[" + LABEL_CHAR - + "\\;\\/\\?\\:\\@\\&\\=\\#\\~" // plus optional query params - + "\\-\\.\\+\\!\\*\\'\\(\\)\\,\\_])|(?:\\%[a-fA-F0-9]{2}))*"; + + PATH_AND_QUERY_CHARS_WITHOUT_SLASH + + "]+[^" + PATH_AND_QUERY_CHARS_WITHOUT_SLASH + "\\s]" // path and query chars must not be trailing + + "|\\/" // trailing slashes are fine + + ")|(?:\\%[a-fA-F0-9]{2}))*"; + /** * Regular expression pattern to match most part of RFC 3987 * Internationalized URLs, aka IRIs. From 4040d5f6471d6c958321902f335014bdaeedbfa2 Mon Sep 17 00:00:00 2001 From: Millesimus Date: Sat, 4 Sep 2021 16:01:23 +0200 Subject: [PATCH 69/82] Treat dollar signs as URI chars (fixing #3859). --- src/main/java/eu/siacs/conversations/utils/Patterns.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/eu/siacs/conversations/utils/Patterns.java b/src/main/java/eu/siacs/conversations/utils/Patterns.java index d220b3326..0ee96b74c 100644 --- a/src/main/java/eu/siacs/conversations/utils/Patterns.java +++ b/src/main/java/eu/siacs/conversations/utils/Patterns.java @@ -306,7 +306,7 @@ public class Patterns { private static final String PORT_NUMBER = "\\:\\d{1,5}"; private static final String PATH_AND_QUERY_CHARS_WITHOUT_SLASH = "\\;\\?\\:\\@\\&\\=\\#\\~" // plus optional query params - + "\\-\\.\\+\\!\\*\\'\\(\\)\\,\\_"; + + "\\-\\.\\+\\!\\*\\'\\(\\)\\,\\_\\$"; private static final String PATH_AND_QUERY = "\\/(?:(?:[" + LABEL_CHAR + PATH_AND_QUERY_CHARS_WITHOUT_SLASH + "]+[^" + PATH_AND_QUERY_CHARS_WITHOUT_SLASH + "\\s]" // path and query chars must not be trailing From ca08c27eef6493d799fdd6600ac8cc9e36ba0002 Mon Sep 17 00:00:00 2001 From: Millesimus Date: Sat, 4 Sep 2021 17:33:25 +0200 Subject: [PATCH 70/82] Parse IPv6 URIs (#3841). --- .../siacs/conversations/utils/Patterns.java | 39 +++++++++++++++++-- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/utils/Patterns.java b/src/main/java/eu/siacs/conversations/utils/Patterns.java index 0ee96b74c..02a437345 100644 --- a/src/main/java/eu/siacs/conversations/utils/Patterns.java +++ b/src/main/java/eu/siacs/conversations/utils/Patterns.java @@ -254,6 +254,39 @@ public class Patterns { + "[0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1]" + "[0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}" + "|[1-9][0-9]|[0-9]))"); + + /** + * IPv6 address matcher for + * IPv6 addresses + * zero compressed IPv6 addresses (section 2.2 of rfc5952) + * link-local IPv6 addresses with zone index (section 11 of rfc4007) + * IPv4-Embedded IPv6 Address (section 2 of rfc6052) + * IPv4-mapped IPv6 addresses (section 2.1 of rfc2765) + * IPv4-translated addresses (section 2.1 of rfc2765) + * + * Taken from https://stackoverflow.com/questions/53497/regular-expression-that-matches-valid-ipv6-addresses/17871737#17871737 + */ + public static final Pattern IP6_ADDRESS + = Pattern.compile( + "\\[" + + "(([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|" + + "([0-9a-fA-F]{1,4}:){1,7}:|" + + "([0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|" + + "([0-9a-fA-F]{1,4}:){1,5}(:[0-9a-fA-F]{1,4}){1,2}|" + + "([0-9a-fA-F]{1,4}:){1,4}(:[0-9a-fA-F]{1,4}){1,3}|" + + "([0-9a-fA-F]{1,4}:){1,3}(:[0-9a-fA-F]{1,4}){1,4}|" + + "([0-9a-fA-F]{1,4}:){1,2}(:[0-9a-fA-F]{1,4}){1,5}|" + + "[0-9a-fA-F]{1,4}:((:[0-9a-fA-F]{1,4}){1,6})|" + + ":((:[0-9a-fA-F]{1,4}){1,7}|:)|" + + "fe80:(:[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|" + + "::(ffff(:0{1,4}){0,1}:){0,1}" + + "((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}" + + "(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])|" + + "([0-9a-fA-F]{1,4}:){1,4}:" + + "((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\\.){3,3}" + + "(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9]))" + + "\\]" + ); /** * Valid UCS characters defined in RFC 3987. Excludes space characters. */ @@ -296,7 +329,7 @@ public class Patterns { private static final String TLD = "(" + PUNYCODE_TLD + "|" + "[" + TLD_CHAR + "]{2,63}" +")"; private static final String HOST_NAME = "(" + IRI_LABEL + "\\.)+" + TLD; public static final Pattern DOMAIN_NAME - = Pattern.compile("(" + HOST_NAME + "|" + IP_ADDRESS + ")"); + = Pattern.compile("(" + HOST_NAME + "|" + IP6_ADDRESS + "|" + IP_ADDRESS +")"); private static final String PROTOCOL = "(?i:http|https|rtsp):\\/\\/"; /* A word boundary or end of input. This is to stop foo.sure from matching as foo.su */ private static final String WORD_BOUNDARY = "(?:\\b|$|^)"; @@ -341,12 +374,12 @@ public class Patterns { * {@link #IP_ADDRESS} */ private static final Pattern STRICT_DOMAIN_NAME - = Pattern.compile("(?:" + STRICT_HOST_NAME + "|" + IP_ADDRESS + ")"); + = Pattern.compile("(?:" + STRICT_HOST_NAME + "|" + IP_ADDRESS + "|" + IP6_ADDRESS + ")"); /** * Regular expression that matches domain names without a TLD */ private static final String RELAXED_DOMAIN_NAME = - "(?:" + "(?:" + IRI_LABEL + "(?:\\.(?=\\S))" +"?)+" + "|" + IP_ADDRESS + ")"; + "(?:" + "(?:" + IRI_LABEL + "(?:\\.(?=\\S))" +"?)+" + "|" + IP_ADDRESS + "|" + IP6_ADDRESS + ")"; /** * Regular expression to match strings that do not start with a supported protocol. The TLDs * are expected to be one of the known TLDs. From 63f5f8c89d46ce5bbb100d9f1de39a7bf37e9e92 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 8 Sep 2021 10:47:34 +0200 Subject: [PATCH 71/82] modify TODOs in JingleRtpConnection upon better understanding of the WebRTC stack --- .../xmpp/jingle/JingleRtpConnection.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) 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 67c32d72a..6afcff336 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/jingle/JingleRtpConnection.java @@ -1038,6 +1038,7 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web return RtpEndUserState.CONNECTING; } case SESSION_ACCEPTED: + //TODO refactor this out into separate method (that uses switch for better readability) final PeerConnection.PeerConnectionState state; try { state = webRTCWrapper.getState(); @@ -1340,9 +1341,13 @@ public class JingleRtpConnection extends AbstractJingleConnection implements Web if (newState == PeerConnection.PeerConnectionState.CLOSED && this.rtpConnectionEnded == 0) { this.rtpConnectionEnded = SystemClock.elapsedRealtime(); } - //TODO 'DISCONNECTED' might be an opportunity to renew the offer and send a transport-replace - //TODO exact syntax is yet to be determined but transport-replace sounds like the most reasonable - //as there is no content-replace + //TODO 'failed' means we need to restart ICE + // + //TODO 'disconnected' can probably be ignored as "This is a less stringent test than failed + // and may trigger intermittently and resolve just as spontaneously on less reliable networks, + // or during temporary disconnections. When the problem resolves, the connection may return + // to the connected state." + // Obviously the UI needs to reflect this new state with a 'reconnecting' display or something if (Arrays.asList(PeerConnection.PeerConnectionState.FAILED, PeerConnection.PeerConnectionState.DISCONNECTED).contains(newState)) { if (isTerminated()) { Log.d(Config.LOGTAG, id.account.getJid().asBareJid() + ": not sending session-terminate after connectivity error because session is already in state " + this.state); From 5a9777f7f1069370f029228dc8ac20458465812c Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 8 Sep 2021 11:13:22 +0200 Subject: [PATCH 72/82] version bump to 2.10.0-beta.2 + changelog --- CHANGELOG.md | 1 + build.gradle | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90bf2a78e..48a5feff5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### Version 2.10.0 * Show black bars when remote video does not match aspect ratio of screen +* Improve search performance * Add setting to prevent screenshots ### Version 2.9.13 diff --git a/build.gradle b/build.gradle index 36bf91f18..4a80ccaaf 100644 --- a/build.gradle +++ b/build.gradle @@ -93,8 +93,8 @@ android { defaultConfig { minSdkVersion 21 targetSdkVersion 29 - versionCode 42016 - versionName "2.10.0-beta" + versionCode 42017 + versionName "2.10.0-beta.2" archivesBaseName += "-$versionName" applicationId "eu.siacs.conversations" resValue "string", "applicationId", applicationId From dfeeaff74ca05d37b1a0fe5e721e12832e2124dc Mon Sep 17 00:00:00 2001 From: Millesimus Date: Wed, 8 Sep 2021 15:25:47 +0200 Subject: [PATCH 73/82] >.< should not be rendered as quote (bugfix). --- src/main/java/eu/siacs/conversations/utils/UIHelper.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/eu/siacs/conversations/utils/UIHelper.java b/src/main/java/eu/siacs/conversations/utils/UIHelper.java index 4c2c0b585..26732b501 100644 --- a/src/main/java/eu/siacs/conversations/utils/UIHelper.java +++ b/src/main/java/eu/siacs/conversations/utils/UIHelper.java @@ -422,6 +422,7 @@ public class UIHelper { final char first = body.charAt(pos + 1); return first == ';' || first == ':' + || first == '.' // do not quote >.< (but >>.<) || closingBeforeWhitespace(body, pos + 1); } } From 4d36231fa57a9faed345e625a474c807c21272ef Mon Sep 17 00:00:00 2001 From: Millesimus Date: Wed, 8 Sep 2021 16:32:44 +0200 Subject: [PATCH 74/82] >.< should be quoteable (bugfix). --- src/main/java/eu/siacs/conversations/ui/util/QuoteHelper.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/ui/util/QuoteHelper.java b/src/main/java/eu/siacs/conversations/ui/util/QuoteHelper.java index 4e4617cab..ac2913037 100644 --- a/src/main/java/eu/siacs/conversations/ui/util/QuoteHelper.java +++ b/src/main/java/eu/siacs/conversations/ui/util/QuoteHelper.java @@ -77,10 +77,10 @@ public class QuoteHelper { } public static boolean isNestedTooDeeply (CharSequence line){ - if (isPositionQuoteCharacter(line, 0)) { + if (isPositionQuoteStart(line, 0)) { int nestingDepth = 1; for (int i = 1; i < line.length(); i++) { - if (isPositionQuoteCharacter(line, i)) { + if (isPositionQuoteStart(line, i)) { nestingDepth++; } if (nestingDepth > (Config.QUOTING_MAX_DEPTH - 1)) { From 3135550b83a4aa05a080567e8f7914040fc723e4 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 8 Sep 2021 20:53:11 +0200 Subject: [PATCH 75/82] pulled translations from transifex --- src/main/res/values-ja/strings.xml | 2 +- src/main/res/values-tr-rTR/strings.xml | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/res/values-ja/strings.xml b/src/main/res/values-ja/strings.xml index 1dde2ca62..a5cb3837e 100644 --- a/src/main/res/values-ja/strings.xml +++ b/src/main/res/values-ja/strings.xml @@ -727,7 +727,7 @@ XMPP アドレスをコピー S3 の HTTP ファイル共有 直接検索 - ‘会話の開始’画面でキーボードを開き、検索フィールドにカーソルを置きます + ‘会話を開始’画面でキーボードを開き、検索フィールドにカーソルを置きます グループチャットのアバター ホストはグループチャットのアバターをサポートしていません 所有者だけが、グループチャットのアバターを変更可能です diff --git a/src/main/res/values-tr-rTR/strings.xml b/src/main/res/values-tr-rTR/strings.xml index 4f0084189..72094ced9 100644 --- a/src/main/res/values-tr-rTR/strings.xml +++ b/src/main/res/values-tr-rTR/strings.xml @@ -132,6 +132,8 @@ Yığın izi göndererek gelişime yardımcı oluyorsunuz. İletileri onayla Onların iletilerini aldığınızda ve okuduğunuzda, kişilerinizin bunu bilmesini sağlayın + Ekran görüntülerini engelle + Uygulama anahtarlayıcısında uygulama içeriklerini sakla ve ekran görüntülerini engelle Arabirim OpenKeychain bir hata verdi. Kötü anahar şifrelemesi. @@ -414,6 +416,7 @@ ses video görüntü + Vektör grafik PDF belgesi Android uygulaması Kişi @@ -912,6 +915,7 @@ Bağlantı kesildi Geri çekilmiş arama Uygulama hatası + Doğrulama sorunu Çağrıyı sonlandır Devam eden arama Deaam eden görüntülü arama @@ -963,4 +967,6 @@ Bu özelliği destekleyen aktif bir hesap yok Yedekleme başlatıldı. Tamamlandığı zaman bir bildirim alacaksınız. Video etkinleştirilemedi - + Düz metin dosyası + + From 2957bccb33fe36b426feb88eedfc675a3b03a28c Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Wed, 8 Sep 2021 21:30:21 +0200 Subject: [PATCH 76/82] Revert "Fixing trailing characters treated as part of URI error (#3938)." This reverts commit 8d45cc5827f9fb3f6535749cc7d5c624fc08f5b5. --- .../java/eu/siacs/conversations/utils/Patterns.java | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/utils/Patterns.java b/src/main/java/eu/siacs/conversations/utils/Patterns.java index 02a437345..026951b22 100644 --- a/src/main/java/eu/siacs/conversations/utils/Patterns.java +++ b/src/main/java/eu/siacs/conversations/utils/Patterns.java @@ -337,15 +337,9 @@ public class Patterns { + "\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,64}(?:\\:(?:[a-zA-Z0-9\\$\\-\\_" + "\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,25})?\\@"; private static final String PORT_NUMBER = "\\:\\d{1,5}"; - private static final String PATH_AND_QUERY_CHARS_WITHOUT_SLASH = - "\\;\\?\\:\\@\\&\\=\\#\\~" // plus optional query params - + "\\-\\.\\+\\!\\*\\'\\(\\)\\,\\_\\$"; private static final String PATH_AND_QUERY = "\\/(?:(?:[" + LABEL_CHAR - + PATH_AND_QUERY_CHARS_WITHOUT_SLASH - + "]+[^" + PATH_AND_QUERY_CHARS_WITHOUT_SLASH + "\\s]" // path and query chars must not be trailing - + "|\\/" // trailing slashes are fine - + ")|(?:\\%[a-fA-F0-9]{2}))*"; - + + "\\;\\/\\?\\:\\@\\&\\=\\#\\~" // plus optional query params + + "\\-\\.\\+\\!\\*\\'\\(\\)\\,\\_\\$])|(?:\\%[a-fA-F0-9]{2}))*"; /** * Regular expression pattern to match most part of RFC 3987 * Internationalized URLs, aka IRIs. @@ -516,4 +510,4 @@ public class Patterns { * Do not create this static utility class. */ private Patterns() {} -} \ No newline at end of file +} From 8d9c51d7558a58013f8f4c517c760499cfa3d78e Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Fri, 10 Sep 2021 10:25:31 +0200 Subject: [PATCH 77/82] pulled translations from transifex --- src/main/res/values-it/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/res/values-it/strings.xml b/src/main/res/values-it/strings.xml index a4544d798..a1835ab4c 100644 --- a/src/main/res/values-it/strings.xml +++ b/src/main/res/values-it/strings.xml @@ -416,6 +416,7 @@ audio video immagine + grafica vettoriale Documento PDF Applicazione Android Contatto From d436c5f856d1c242b0f39fb7c2828af6ad42de81 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Fri, 10 Sep 2021 18:46:10 +0200 Subject: [PATCH 78/82] catch exception when trying to read display name. fixes #4163 --- src/main/java/eu/siacs/conversations/utils/MimeUtils.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/main/java/eu/siacs/conversations/utils/MimeUtils.java b/src/main/java/eu/siacs/conversations/utils/MimeUtils.java index b320c0c11..76d4911b8 100644 --- a/src/main/java/eu/siacs/conversations/utils/MimeUtils.java +++ b/src/main/java/eu/siacs/conversations/utils/MimeUtils.java @@ -568,6 +568,8 @@ public final class MimeUtils { if (cursor != null && cursor.moveToFirst()) { return cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); } + } catch (Exception e) { + return null; } return null; } From 25f137441b2753bf4438c4b3ebd09703fdb8b13b Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Fri, 10 Sep 2021 18:46:37 +0200 Subject: [PATCH 79/82] catch security exception when viewing file from media preview --- .../siacs/conversations/ui/adapter/MediaPreviewAdapter.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/MediaPreviewAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/MediaPreviewAdapter.java index 73f24fe15..44a3835e0 100644 --- a/src/main/java/eu/siacs/conversations/ui/adapter/MediaPreviewAdapter.java +++ b/src/main/java/eu/siacs/conversations/ui/adapter/MediaPreviewAdapter.java @@ -75,8 +75,10 @@ public class MediaPreviewAdapter extends RecyclerView.Adapter Date: Fri, 10 Sep 2021 19:07:57 +0200 Subject: [PATCH 80/82] run file observer on its own thread. fixes #4164 --- .../services/XmppConnectionService.java | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 1f3b666ac..1c91c1ee8 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -75,6 +75,8 @@ import java.util.Set; import java.util.WeakHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; @@ -184,8 +186,9 @@ public class XmppConnectionService extends Service { private static final String SETTING_LAST_ACTIVITY_TS = "last_activity_timestamp"; public final CountDownLatch restoredFromDatabaseLatch = new CountDownLatch(1); - private final SerialSingleThreadExecutor mFileAddingExecutor = new SerialSingleThreadExecutor("FileAdding"); - private final SerialSingleThreadExecutor mVideoCompressionExecutor = new SerialSingleThreadExecutor("VideoCompression"); + private final static Executor FILE_OBSERVER_EXECUTOR = Executors.newSingleThreadExecutor(); + private final static Executor FILE_ATTACHMENT_EXECUTOR = Executors.newSingleThreadExecutor(); + private final static SerialSingleThreadExecutor VIDEO_COMPRESSION_EXECUTOR = new SerialSingleThreadExecutor("VideoCompression"); private final SerialSingleThreadExecutor mDatabaseWriterExecutor = new SerialSingleThreadExecutor("DatabaseWriter"); private final SerialSingleThreadExecutor mDatabaseReaderExecutor = new SerialSingleThreadExecutor("DatabaseReader"); private final SerialSingleThreadExecutor mNotificationExecutor = new SerialSingleThreadExecutor("NotificationExecutor"); @@ -563,9 +566,9 @@ public class XmppConnectionService extends Service { Log.d(Config.LOGTAG, "counterpart=" + message.getCounterpart()); final AttachFileToConversationRunnable runnable = new AttachFileToConversationRunnable(this, uri, type, message, callback); if (runnable.isVideoMessage()) { - mVideoCompressionExecutor.execute(runnable); + VIDEO_COMPRESSION_EXECUTOR.execute(runnable); } else { - mFileAddingExecutor.execute(runnable); + FILE_ATTACHMENT_EXECUTOR.execute(runnable); } } @@ -592,7 +595,7 @@ public class XmppConnectionService extends Service { message.setType(Message.TYPE_IMAGE); } Log.d(Config.LOGTAG, "attachImage: type=" + message.getType()); - mFileAddingExecutor.execute(() -> { + FILE_ATTACHMENT_EXECUTOR.execute(() -> { try { getFileBackend().copyImageToPrivateStorage(message, uri); } catch (FileBackend.ImageCompressionException e) { @@ -1146,11 +1149,11 @@ public class XmppConnectionService extends Service { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M || ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) { startContactObserver(); } - mFileAddingExecutor.execute(fileBackend::deleteHistoricAvatarPath); + FILE_OBSERVER_EXECUTOR.execute(fileBackend::deleteHistoricAvatarPath); if (Compatibility.hasStoragePermission(this)) { Log.d(Config.LOGTAG, "starting file observer"); - mFileAddingExecutor.execute(this.fileObserver::startWatching); - mFileAddingExecutor.execute(this::checkForDeletedFiles); + FILE_OBSERVER_EXECUTOR.execute(this.fileObserver::startWatching); + FILE_OBSERVER_EXECUTOR.execute(this::checkForDeletedFiles); } if (Config.supportOpenPgp()) { this.pgpServiceConnection = new OpenPgpServiceConnection(this, "org.sufficientlysecure.keychain", new OpenPgpServiceConnection.OnBound() { @@ -1266,8 +1269,8 @@ public class XmppConnectionService extends Service { public void restartFileObserver() { Log.d(Config.LOGTAG, "restarting file observer"); - mFileAddingExecutor.execute(this.fileObserver::restartWatching); - mFileAddingExecutor.execute(this::checkForDeletedFiles); + FILE_OBSERVER_EXECUTOR.execute(this.fileObserver::restartWatching); + FILE_OBSERVER_EXECUTOR.execute(this::checkForDeletedFiles); } public void toggleScreenEventReceiver() { @@ -1927,7 +1930,7 @@ public class XmppConnectionService extends Service { private void restoreMessages(Conversation conversation) { conversation.addAll(0, databaseBackend.getMessages(conversation, Config.PAGE_SIZE)); conversation.findUnsentTextMessages(message -> markMessage(message, Message.STATUS_WAITING)); - conversation.findUnreadMessages(message -> mNotificationService.pushFromBacklog(message)); + conversation.findUnreadMessages(mNotificationService::pushFromBacklog); } public void loadPhoneContacts() { From 68d8e2b9cf4b3b89b06fa0118f3bf9d1e06ee589 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 11 Sep 2021 09:55:44 +0200 Subject: [PATCH 81/82] delete targe file after unsuccessful image compression --- .../persistance/FileBackend.java | 51 ++++++++++++------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java index 74c913162..48ffae422 100644 --- a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java @@ -33,6 +33,8 @@ import androidx.annotation.StringRes; import androidx.core.content.FileProvider; import androidx.exifinterface.media.ExifInterface; +import com.google.common.io.ByteStreams; + import java.io.ByteArrayOutputStream; import java.io.Closeable; import java.io.File; @@ -627,20 +629,20 @@ public class FileBackend { private void copyFileToPrivateStorage(File file, Uri uri) throws FileCopyException { Log.d(Config.LOGTAG, "copy file (" + uri.toString() + ") to private storage " + file.getAbsolutePath()); file.getParentFile().mkdirs(); - OutputStream os = null; - InputStream is = null; try { file.createNewFile(); - os = new FileOutputStream(file); - is = mXmppConnectionService.getContentResolver().openInputStream(uri); - byte[] buffer = new byte[1024]; - int length; - while ((length = is.read(buffer)) > 0) { - try { - os.write(buffer, 0, length); - } catch (IOException e) { - throw new FileWriterException(); - } + } catch (IOException e) { + throw new FileCopyException(R.string.error_unable_to_create_temporary_file); + } + try (final OutputStream os = new FileOutputStream(file); + final InputStream is = mXmppConnectionService.getContentResolver().openInputStream(uri)) { + if (is == null) { + throw new FileCopyException(R.string.error_file_not_found); + } + try { + ByteStreams.copy(is, os); + } catch (IOException e) { + throw new FileWriterException(); } try { os.flush(); @@ -648,16 +650,17 @@ public class FileBackend { throw new FileWriterException(); } } catch (final FileNotFoundException e) { + cleanup(file); throw new FileCopyException(R.string.error_file_not_found); } catch (final FileWriterException e) { + cleanup(file); throw new FileCopyException(R.string.error_unable_to_create_temporary_file); } catch (final SecurityException e) { + cleanup(file); throw new FileCopyException(R.string.error_security_exception); } catch (final IOException e) { + cleanup(file); throw new FileCopyException(R.string.error_io_exception); - } finally { - close(os); - close(is); } } @@ -708,7 +711,7 @@ public class FileBackend { private void copyImageToPrivateStorage(File file, Uri image, int sampleSize) throws FileCopyException, ImageCompressionException { final File parent = file.getParentFile(); - if (parent.mkdirs()) { + if (parent != null && parent.mkdirs()) { Log.d(Config.LOGTAG, "created parent directory"); } InputStream is = null; @@ -753,13 +756,15 @@ public class FileBackend { } scaledBitmap.recycle(); } catch (final FileNotFoundException e) { + cleanup(file); throw new FileCopyException(R.string.error_file_not_found); - } catch (IOException e) { - e.printStackTrace(); + } catch (final IOException e) { + cleanup(file); throw new FileCopyException(R.string.error_io_exception); } catch (SecurityException e) { + cleanup(file); throw new FileCopyException(R.string.error_security_exception_during_image_copy); - } catch (OutOfMemoryError e) { + } catch (final OutOfMemoryError e) { ++sampleSize; if (sampleSize <= 3) { copyImageToPrivateStorage(file, image, sampleSize); @@ -772,6 +777,14 @@ public class FileBackend { } } + private static void cleanup(final File file) { + try { + file.delete(); + } catch (Exception e) { + + } + } + public void copyImageToPrivateStorage(File file, Uri image) throws FileCopyException, ImageCompressionException { Log.d(Config.LOGTAG, "copy image (" + image.toString() + ") to private storage " + file.getAbsolutePath()); copyImageToPrivateStorage(file, image, 0); From 3f315751a12fb8178d44733289e77197c7a0f296 Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sat, 11 Sep 2021 10:28:34 +0200 Subject: [PATCH 82/82] version bump to 2.10.0 --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 4a80ccaaf..1fcf2d8bc 100644 --- a/build.gradle +++ b/build.gradle @@ -93,8 +93,8 @@ android { defaultConfig { minSdkVersion 21 targetSdkVersion 29 - versionCode 42017 - versionName "2.10.0-beta.2" + versionCode 42018 + versionName "2.10.0" archivesBaseName += "-$versionName" applicationId "eu.siacs.conversations" resValue "string", "applicationId", applicationId