Compare commits

...

430 Commits

Author SHA1 Message Date
genofire aa8b9f338f fix usage directTLS of manuelle enter an address 2021-12-11 16:42:31 +01:00
genofire 658c1c58d5 Merge remote-tracking branch 'upstream/master' into develop 2021-11-25 21:20:44 +01:00
genofire 39792f0815 fix Language merge 2021-11-20 22:37:21 +01:00
Daniel Gultsch db834a1f07 indicate call reconnect in notification 2021-11-19 12:26:11 +01:00
Daniel Gultsch f8a94161db don't play tone going from connect->reconnect->connect 2021-11-19 12:25:27 +01:00
Daniel Gultsch 5d526a77e3 include uncertainty into shared geo uri 2021-11-18 11:24:10 +01:00
Daniel Gultsch a508a81553 externalize rtc config generation into seperate method 2021-11-17 11:33:15 +01:00
Daniel Gultsch 61fb38cd84 clean up some error handling error ICE restarts 2021-11-17 10:49:16 +01:00
Daniel Gultsch 1bf2d5dd8f video calls: leave full screen mode during reconnect 2021-11-16 22:01:48 +01:00
Daniel Gultsch 0a18c8613f assume credentials are the same for all contents when restarting ICE 2021-11-16 17:08:34 +01:00
Daniel Gultsch abb671616c synchronize setDescription calls 2021-11-16 15:17:12 +01:00
Daniel Gultsch 297a843b9c use implicit rollback (needed to be enabled on libwebrtc) 2021-11-16 13:17:10 +01:00
Daniel Gultsch 0698fa0d8c store peer dtls setup for later use in ice restart 2021-11-16 11:21:11 +01:00
Daniel Gultsch 70b5d8d81a set proper peer dtls setup on ice restart received 2021-11-15 21:49:31 +01:00
Daniel Gultsch 0a3947b8e3 terminate with application failure when failing to apply ICE restart
This is fairly unlikely to happen in practice
2021-11-15 17:18:45 +01:00
Daniel Gultsch 3f402b132b respond with tie-break to prevent ICE restart race 2021-11-15 13:03:04 +01:00
Daniel Gultsch 5b80c62a63 treat transport-info w/o candidates and changed credentials as offer 2021-11-14 18:22:18 +01:00
Daniel Gultsch 717c83753f delay discovered ice candidates until JingleRtpConnection is ready to receive
otherwise setLocalDescription and the arrival of first candidates might race
the rtpContentDescription being set
2021-11-11 21:02:17 +01:00
Daniel Gultsch b6dee6da6a reverse: webrtc: include oldState in onConnectionChange
turns out we don’t need it and a better way is for RtpConnection to keep track of *all*
states in the current generation
2021-11-11 17:05:36 +01:00
Daniel Gultsch 9c3f55bef2 use stopwatch to keep track of jingle rtp session duration 2021-11-11 16:52:18 +01:00
Daniel Gultsch 9843b72f6f always use Camera2Enumerator 2021-11-11 15:23:45 +01:00
Daniel Gultsch 61851e5f84 do not automacially hang up failed webrtc sessions 2021-11-11 14:40:15 +01:00
Daniel Gultsch 4ec0996dff webrtc: include oldState in onConnectionChange 2021-11-11 11:19:37 +01:00
genofire fa1363cea0 Merge tag '2.10.2' into develop 2021-11-11 00:28:16 +01:00
Daniel Gultsch fda45a7c86 use implicit descriptions for WebRTC
using the parameter-free form of setLocalDescription() solves some potential race conditions
that can occur - especially once we introduce restartIce()
2021-11-10 16:40:24 +01:00
Daniel Gultsch b5786787f0 bump libphone number library 2021-11-09 14:27:26 +01:00
Daniel Gultsch d4cbf2e11e take intent type into account when sharing with conversations 2021-11-07 11:35:00 +01:00
Daniel Gultsch 7d7e158fd7 code clean up for LocationProvider 2021-11-03 16:00:26 +01:00
Daniel Gultsch a8ff88398d version bump to 2.10.2 + changelog 2021-11-03 15:59:05 +01:00
Daniel Gultsch bae9fc45c2 make rtcpMux optional 2021-10-31 10:20:58 +01:00
Daniel Gultsch ba4a47204b fixed IndexOutOfBounds when rendering quotes 2021-10-31 10:20:34 +01:00
Daniel Gultsch 226eb739bd make custom 'xmpp' protocol in address book case insensitve
fixes #4215
2021-10-31 08:35:44 +01:00
Daniel Gultsch 869a135bab bump okhttp 2021-10-20 10:19:59 +02:00
Daniel Gultsch 7ddd60d314 bump jxmpp. fix crash in magic create when entering @ 2021-10-20 10:19:46 +02:00
Daniel Gultsch 2ca00265db bump speed dial version to something that uses AndroidX 2021-10-20 09:52:10 +02:00
Daniel Gultsch e0c4964cc2 bump gradle plugin version 2021-10-14 17:32:07 +02:00
Daniel Gultsch 3706981645 fix mime type detection in urls that have query params or an anchor 2021-10-14 17:30:55 +02:00
genofire a124b3df9a metadata: add changelog 2.10.1 2021-10-13 18:19:29 +02:00
genofire 5a7bae592d Merge tag '2.10.1' into develop 2021-10-13 18:14:50 +02:00
Daniel Gultsch 6d2e406ee5 attempt to parse Link header from https url scanned from welcome screen 2021-10-07 09:48:49 +02:00
Daniel Gultsch cc489ef7bf bump version code 2021-10-06 12:33:36 +02:00
Daniel Gultsch 495537d087 minor code cleanup in UriHandlerActivity 2021-10-06 12:18:58 +02:00
Daniel Gultsch 20e4d108d4 fixed regression of not handling jingle content map parsing failures 2021-10-05 15:43:05 +02:00
Daniel Gultsch d0af5a002e leave code comment about xmpp vs jabber vcard entry 2021-10-04 15:18:37 +02:00
Daniel Gultsch 86de21f6a8 allow encrypted backups. fixes #4190 2021-10-04 14:17:01 +02:00
Daniel Gultsch e664a27cd0 fix typo in action matcher 2021-10-03 18:51:18 +02:00
Daniel Gultsch 4a6df90f0c attempt to read both jabber and xmpp IM fields from address book 2021-10-03 17:26:24 +02:00
Daniel Gultsch fdaab1c27e remove unused import in favor of fqn 2021-10-03 17:01:51 +02:00
Daniel Gultsch f8c59a7b75 support imto://xmpp intents 2021-10-03 17:01:32 +02:00
Daniel Gultsch f182fe6697 use PM on direct reply if last message in notifacation stack is PM 2021-10-03 16:38:30 +02:00
Daniel Gultsch daf1bbfca5 bump version code 2021-10-02 19:49:18 +02:00
Daniel Gultsch b8eec6ae5b pulled translations from transifex 2021-10-02 16:59:39 +02:00
Daniel Gultsch 3ede2d00bd remove logging 2021-10-02 16:54:19 +02:00
Daniel Gultsch d2a387e82f correctly calculate socks destination 2021-10-02 16:44:36 +02:00
Daniel Gultsch da14f83a42 ensure all bytes are read in socks handshake. fixes #4188 2021-10-02 14:24:36 +02:00
Daniel Gultsch 586fff5485 Quicksy: theme choose country activity 2021-09-29 10:51:25 +02:00
Daniel Gultsch ea9b73c1fe Quicksy: fix drawables not being styled in enter phone number screen 2021-09-29 10:42:26 +02:00
Daniel Gultsch e791e19265 ignore non letters when parsing action from xmpp uri 2021-09-27 11:15:56 +02:00
Daniel Gultsch 3de8147b41 pulled translations from transifex 2021-09-27 10:48:04 +02:00
Daniel Gultsch b9ceb67104 version bump to 2.10.1 + changelog 2021-09-24 09:25:27 +02:00
Daniel Gultsch 90a0d36362 fix not recognizing message as download. fixes #4178 2021-09-24 09:15:21 +02:00
Daniel Gultsch bd4d939a29 backport requireActivity method 2021-09-21 11:55:37 +02:00
Daniel Gultsch 64a6edd3fb Revert "Migrate Fragments to AndroidX"
This reverts commit 231d97ea81.

Migrating to AndroidX Fragments seems to have some unforseen side effects
and no clear benefits
2021-09-21 11:41:35 +02:00
Daniel Gultsch 75c20a7a2b handle on-device contacts with unstable system uri
on device contacts (contacts not synced) have an unstable system uri.
For quicksy.im contacts we can identify the contact based on the phone number
instead.

fixes #4174
2021-09-21 10:20:23 +02:00
Daniel Gultsch d5994a8d65 add to address book should add phone number for Quicksy+quicksy.im
fixes #4165
2021-09-21 10:19:09 +02:00
Daniel Gultsch 572b9c2dc6 pulled translations from transifex 2021-09-20 11:29:35 +02:00
Daniel Gultsch f9f994c540 Intent.EXTRA_ALLOW_MULTIPLE is now supported by minSdk 2021-09-20 10:08:11 +02:00
Daniel Gultsch ba9596b37d catch rare exception around execute pending fragment transactions 2021-09-20 10:07:38 +02:00
Daniel Gultsch b01bca74fd fix some linter warnings 2021-09-20 09:54:42 +02:00
Daniel Gultsch bfc8668803 bump appcompat version 2021-09-20 09:27:40 +02:00
Alexei Sorokin 951d84f404 make sure messages_index is always cleaned up fully. fixes #4170 2021-09-20 06:26:35 +00:00
Ashique Bava 231d97ea81
Migrate Fragments to AndroidX 2021-09-20 06:22:55 +00:00
Daniel Gultsch 73000962fe bump transcoder version
fixes #4167
2021-09-15 21:54:03 +02:00
Daniel Gultsch 3075833ab3 swap out transcoder library
the transcoder library we used hasn’t been updated in years

this commit switches to a maintained fork https://natario1.github.io/Transcoder/
2021-09-15 11:38:06 +02:00
Geno eba5dd8654 metadata: add changelog 2.10.0 2021-09-13 00:04:35 +02:00
Geno abe3718353 Merge tag '2.10.0' into develop 2021-09-13 00:02:16 +02:00
Daniel Gultsch 3f315751a1 version bump to 2.10.0 2021-09-11 10:28:34 +02:00
Daniel Gultsch 68d8e2b9cf delete targe file after unsuccessful image compression 2021-09-11 09:55:44 +02:00
Daniel Gultsch c195e8b3d2 run file observer on its own thread. fixes #4164 2021-09-10 19:07:57 +02:00
Daniel Gultsch 25f137441b catch security exception when viewing file from media preview 2021-09-10 18:46:37 +02:00
Daniel Gultsch d436c5f856 catch exception when trying to read display name. fixes #4163 2021-09-10 18:46:10 +02:00
Daniel Gultsch 8d9c51d755 pulled translations from transifex 2021-09-10 10:25:31 +02:00
Daniel Gultsch 2957bccb33 Revert "Fixing trailing characters treated as part of URI error (#3938)."
This reverts commit 8d45cc5827.
2021-09-08 21:30:21 +02:00
Daniel Gultsch 3135550b83 pulled translations from transifex 2021-09-08 20:53:11 +02:00
Millesimus 4d36231fa5 >.< should be quoteable (bugfix). 2021-09-08 16:29:01 +00:00
Millesimus dfeeaff74c >.< should not be rendered as quote (bugfix). 2021-09-08 16:29:01 +00:00
Daniel Gultsch 5a9777f7f1 version bump to 2.10.0-beta.2 + changelog 2021-09-08 11:13:22 +02:00
Daniel Gultsch 63f5f8c89d modify TODOs in JingleRtpConnection upon better understanding of the WebRTC stack 2021-09-08 10:47:34 +02:00
Millesimus ca08c27eef Parse IPv6 URIs (#3841). 2021-09-07 15:14:09 +00:00
Millesimus 4040d5f647 Treat dollar signs as URI chars (fixing #3859). 2021-09-07 15:14:09 +00:00
Millesimus 8d45cc5827 Fixing trailing characters treated as part of URI error (#3938). 2021-09-07 15:14:09 +00:00
Daniel Gultsch 96f0a09a5d pulled translations from transifex 2021-09-07 16:56:24 +02:00
Maximilian Weiler 38a77dbba6 Fix ImportBackupActivity not covered by screenshot prevention feature 2021-09-07 14:50:50 +00:00
Daniel Gultsch 8b817b3bd8 add database migration for new fts scheme 2021-09-07 16:47:40 +02:00
Alexei Sorokin 754773be55 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.
2021-09-07 16:19:49 +02:00
Alexei Sorokin 4f362aafac 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.
2021-09-07 16:19:40 +02:00
Daniel Gultsch ea0dc558cb use androidx ExifInterface to parse rotation. fixes #4154 2021-09-05 16:29:24 +02:00
Daniel Gultsch 80d8b6dd88
Upload APKs after CI 2021-08-27 07:47:55 +00:00
Daniel Gultsch 2819545a43 click on action bar title should open chat details screen 2021-08-25 20:04:47 +02:00
Daniel Gultsch 9526456d75 pulled translations from transifex 2021-08-25 18:57:20 +02:00
Daniel Gultsch f975b5ddac executePendingTransactions before trying to access secondary_fragment
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.
2021-08-25 18:54:03 +02:00
Daniel Gultsch 581eb511b9 version bump to 2.10.0-beta 2021-08-24 18:48:50 +02:00
Daniel Gultsch af42e34654 Revert "Always show Quote as last action"
This reverts commit e528b9f5df.

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
2021-08-24 16:53:23 +02:00
Daniel Gultsch 0495470ca8 pulled translations from transifex 2021-08-24 15:32:10 +02:00
Daniel Gultsch 6c88a4b4fa reset affiliation when inviting someone not currently in group. fixes #4146 2021-08-24 14:42:50 +02:00
Daniel Gultsch 88d7ddf124 PIP aspect ratio should match video aspect ratio. fixes #4077 2021-08-24 14:40:12 +02:00
Daniel Gultsch e6d8bee035 stop agp7 complaining about missing proguard rules 2021-08-24 13:33:19 +02:00
Daniel Gultsch 208c9d91db dexOptions is no longer used in agp7 2021-08-24 13:02:31 +02:00
Licaon_Kter bf3c1d573b Avoid description repetition screenshots 2021-08-24 11:00:43 +00:00
Daniel Gultsch 4e90c0dbbb
Update android.yml to download webrtc 2021-08-24 09:21:17 +00:00
Daniel Gultsch caefec2fbf
Create android.yml 2021-08-24 09:19:08 +00:00
Daniel Gultsch 339ee8f6ea bump libwebrtc version to m92 2021-08-24 11:15:50 +02:00
Daniel Gultsch b00b8996d5 bump gradle version and agp 2021-08-24 10:13:03 +02:00
Maximilian Weiler 06fbb06aee Remove unused import 2021-08-24 07:15:30 +00:00
Maximilian Weiler ef8f10cc13 Optionally prevent taking screenshots
- Add setting to prevent screenshots
- Enforce using FLAG_SECURE in onResume for each activity
2021-08-24 07:15:30 +00:00
Millesimus b6fe1898e7 Minor duplication fix. 2021-08-24 07:14:34 +00:00
Millesimus 955a6f3fe1 Bugfix for 6cc06bcb98acc05c7677c642adf8ded90ffc8372. 2021-08-24 07:14:34 +00:00
Millesimus a0529a4e1e On quoting, translate French quotes to XEP-0393 quotes. 2021-08-24 07:14:34 +00:00
Millesimus 2db2ca95ce Move differentiation between XEP-0393 quotes and French quotes to QuoteHelper. 2021-08-24 07:14:34 +00:00
Millesimus a0bca08997 Rewrite QuoteHelper to integrate French quotes logics. Also reallow QuoteChars not followed by whitespace as indicated in XEP-0393. 2021-08-24 07:14:34 +00:00
Millesimus 748443cd4e Fixing message preview. 2021-08-24 07:14:34 +00:00
Millesimus 3921f3a940 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. 2021-08-24 07:14:34 +00:00
Millesimus c81c8a62b3 Small refactoring for a more intuitive config. 2021-08-24 07:14:34 +00:00
Millesimus e850900b40 Quoting quotes, limited by nesting depth. 2021-08-24 07:14:34 +00:00
Millesimus 74d60d0131 Implement nested quotes through iteration. 2021-08-24 07:14:34 +00:00
Millesimus 65a72827bc New helper to help with quotes. 2021-08-24 07:14:34 +00:00
Millesimus 309082a9b3 Fixing xmpp:uri bug in channel details. #4139 2021-08-12 08:28:43 +00:00
Licaon_Kter e528b9f5df Always show Quote as last action 2021-08-12 08:28:36 +00:00
Daniel Gultsch 1e1dad780b add .opus file extension to mime table 2021-07-28 16:57:57 +02:00
Daniel Gultsch 0e54cde4bf add omemo media sharing to doap file 2021-07-23 08:04:36 +02:00
Alexei Sorokin b99f9d4f1c make search case-insensitive not only for ASCII 2021-05-26 06:16:20 +00:00
Daniel Gultsch 7466d12505 ring during device discovery 2021-05-22 19:37:20 +02:00
Daniel Gultsch 98ffadd87d log exception when file is not a ceb 2021-05-18 10:11:35 +02:00
Daniel Gultsch af33a57bf2 add description for text/plain 2021-05-17 16:00:00 +02:00
Daniel Gultsch 87f99d3570 Transferables interface needs to differentiate between 0 and null file size 2021-05-17 15:51:21 +02:00
Daniel Gultsch b025265f91 execute status code check on HEAD 2021-05-16 16:17:06 +02:00
Daniel Gultsch 0f3181555a FileParams indicate unavailable file size as null
since 0 is a valid file size we should use null to indicate absence
2021-05-16 15:32:57 +02:00
Daniel Gultsch 76fb0180d6 bump gradle plugin version 2021-05-16 15:29:45 +02:00
Daniel Gultsch e02aaed7d2 call SVGs 'vector graphic' instead of 'image' 2021-05-14 08:37:42 +02:00
Daniel Gultsch c9f1bdc551 fixup: update some libraries 2021-05-13 11:05:26 +02:00
Daniel Gultsch 2b9862adea update some libraries 2021-05-13 10:27:05 +02:00
Daniel Gultsch d91cd3e9e8 bump jxmpp-jid version 2021-05-13 09:19:16 +02:00
Geno e21d49efc8 Merge tag '2.9.13' into develop 2021-05-10 12:38:11 +02:00
Daniel Gultsch 56535e07ff show black bars in video call when video orientation doesn’t match screen orientation
fixes #4056
2021-05-10 09:24:20 +02:00
Dheeraj Chintaluri 60c5906fe9 Update libwebrtc version 2021-05-10 07:20:53 +00:00
Daniel Gultsch 67e5f839f1 ignore crypto callbacks when rtp session has already been terminated 2021-05-08 11:50:18 +02:00
Daniel Gultsch 89012b0f8b synchronize startRinging() to not create multiple vibrate futures 2021-05-08 11:49:24 +02:00
Daniel Gultsch 7476dccc0e do not fetch keys before making call 2021-05-08 11:21:33 +02:00
Daniel Gultsch 9182a300c5 report fingerprint missmatch as securiy exception 2021-05-08 10:35:07 +02:00
Daniel Gultsch faa4c87b5f build omemo session when encountering unknown on RTP proceed 2021-05-08 09:25:51 +02:00
Daniel Gultsch 8d391753d7 encrypt rtp map as future 2021-05-08 08:45:31 +02:00
Daniel Gultsch 337aa4a110 consider Config.REQUIRE_RTP_VERIFICATION on decrypt. fail as future 2021-05-07 22:55:20 +02:00
Daniel Gultsch ddf597e0d3 invoke x509 verification upon receiving prekey message in rtp session 2021-05-06 18:40:35 +02:00
Daniel Gultsch 9c16af25fb bump gradle version 2021-05-06 17:57:06 +02:00
Daniel Gultsch e2324209ed make sure omemo sessions are verified if the the respective config flag is set 2021-05-04 19:04:01 +02:00
Daniel Gultsch 9544b994dc invoke omemo trust/fetch activity when triggering phone call in require_verification mode 2021-05-04 17:52:17 +02:00
Daniel Gultsch 3b25fb9038 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.
2021-05-04 10:49:45 +02:00
Daniel Gultsch 48156dd27f 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.
2021-05-04 10:10:34 +02:00
Daniel Gultsch 905489e237 bump firebase-messaging libray version 2021-05-04 09:47:09 +02:00
Daniel Gultsch a5ad2b7fc6 version bump to 2.9.13 + changelog 2021-05-03 16:21:10 +02:00
Daniel Gultsch 5d3ad6e36b pulled translations from transifex 2021-05-03 13:14:09 +02:00
Daniel Gultsch 6d91551f59 use onAddTrack instead of deprecated onAddStream 2021-05-03 13:06:42 +02:00
Daniel Gultsch 0717f9ba18 upgrade libwebrtc to m90 and enable extmap-allow-mixed 2021-05-03 09:48:46 +02:00
Daniel Gultsch ac7855a332 show domains in manual cert accept dialog 2021-05-03 08:28:03 +02:00
Daniel Gultsch a40b82b85b version bump to 2.9.12 + changelog 2021-05-02 08:23:00 +02:00
Daniel Gultsch c5e90199c3 trigger registration dialog on roster;ibr=y only if no accounts are configured
fixes #4065
2021-04-30 11:32:42 +02:00
Daniel Gultsch 53908dd56e pulled translations from transifex 2021-04-30 11:00:03 +02:00
Daniel Gultsch 9d9514a091 Add User-Agent to all HTTP calls 2021-04-30 10:54:36 +02:00
Daniel Gultsch bc58fb0fbd Always verify hostname/domain
There might be corner cases where it is required to use self signed
certificates. However there should be no corner cases where it is
required to use a wrong domain name. This commit swaps out the
MemorizingHostnameVerifier that let users accept wrong domains with the
standard XmppDomainVerifier.

closes #4066
2021-04-30 09:55:22 +02:00
Daniel Gultsch ec061bedc1 always show contact permission explain dialog on Quicksy
Until now Conversations and Quicksy would only disply the dialog that explains
why we want contact read permissions after the user rejected the request once

(following Android design guidelines and `shouldShowRequestPermissionRationale()`)

However for PlayStore policy this is no longer enough and the app needs to
explain and ask for consent before starting to upload the data.

This commit now displays the explain dialog immediately before asking for the
first time.
2021-04-24 08:20:30 +02:00
Geno a7f4abbab5 Merge tag '2.9.11' into develop 2021-04-20 12:08:34 +02:00
Daniel Gultsch f9b292fd6a version bump to 2.9.11 + changelog 2021-04-18 18:46:05 +02:00
Daniel Gultsch 8aed588405 ensure vibration future is canceled when scheduling a new one 2021-04-18 16:09:36 +02:00
Daniel Gultsch ea2acc2963 use new hasInternet() API only on Android Q+
some VPN apps are broken on Android 7.1 (and below?)

fixes #4058
2021-04-18 15:47:31 +02:00
Daniel Gultsch 2760f07307 disable read timeout for HTTP Upload
fixes #4057
2021-04-18 15:46:47 +02:00
Daniel Gultsch ec22a39538 link to help.conversations.im from feature list in readme
fixes #4053
2021-04-13 16:10:50 +02:00
Daniel Gultsch 331fd30699 version bump to 2.9.10 + changelog 2021-04-09 15:50:19 +02:00
Daniel Gultsch c469b2dc22 pulled translations from transifex 2021-04-09 15:50:11 +02:00
Daniel Gultsch 202bde46ed properly error out if upload fails. fixes #4052 2021-04-09 15:49:33 +02:00
Daniel Gultsch 37ce311764 do not attempt to play ringtone if none was found 2021-04-08 10:53:01 +02:00
Daniel Gultsch 9fc04c4b1e when receiving out-of-order session-init in terminal state do not move to terminal again
fixes #4049
2021-04-08 10:23:39 +02:00
Daniel Gultsch 5f020af2cc pulled translations from transifex 2021-04-08 09:34:16 +02:00
Daniel Gultsch 55b2f2656d fix HTTP up/download for users that dont trust system CAs 2021-04-08 08:56:58 +02:00
Daniel Gultsch fb681dfd60 ammend 2.9.9 changelog 2021-04-03 09:34:24 +02:00
Daniel Gultsch 5e59f20685 delete issue template and contributing guidelines
people with half a brain will implicitly follow those guidelines any way and
provide the information requested in the issue template.

The vast majority of people have ignored the issue template in the past anyway.
2021-04-03 09:32:24 +02:00
Daniel Gultsch 0fc191d004 migrate hasInternetConnection() to new api
Thank you to @ailicic for figuring out the new API.

Closes #4050
2021-03-29 10:58:15 +02:00
Daniel Gultsch 30c9e7399e log track class in onAddTrack 2021-03-29 10:57:56 +02:00
Geno 35e6c476dd Merge tag '2.9.9' into develop 2021-03-28 10:36:21 +02:00
Daniel Gultsch f632c7bbc9 version bump to 2.9.9 + changelog 2021-03-26 14:54:46 +01:00
Daniel Gultsch 08f27ddcf8 don’t show video call button if no camera is available 2021-03-26 14:04:36 +01:00
Daniel Gultsch 1822a71c2a Do not crash when receiving video call on device w/o camera
Upon accepting a video call on a device that can not establish a video track on
its own (for example by not having a camera), displaying the video enable/disable
button would fail. This commit defaults this button to disabled.
2021-03-26 12:54:26 +01:00
Daniel Gultsch 77f448692c catch security exception when reading file 2021-03-24 10:47:50 +01:00
Daniel Gultsch 9cc95d4cc2 pulled translations from transifex 2021-03-24 09:59:12 +01:00
Daniel Gultsch ff756647a9 clear dns cache on network switch 2021-03-23 21:03:58 +01:00
Daniel Gultsch 9a7fc3d9b8 disable omemo by default for *.covid.monal.im domains 2021-03-23 11:52:34 +01:00
Daniel Gultsch d37140ebf0 pulled translations from transifex 2021-03-23 11:48:17 +01:00
Daniel Gultsch d288f5bff2 version bump to 2.9.9-beta 2021-03-23 10:36:56 +01:00
Daniel Gultsch aad34783ad remove logging from needsUploading() 2021-03-22 19:05:46 +01:00
Daniel Gultsch 914ea9c398 use http proxy below android 7.1 2021-03-22 18:03:25 +01:00
Daniel Gultsch 02b16063c6 show popup dialog when backup has been started. fixes #4031 2021-03-22 15:40:22 +01:00
Daniel Gultsch 45c5f9aa90 bump okhttp version 2021-03-22 15:26:13 +01:00
Daniel Gultsch 4ac64f3a3b clean up code for posh cache 2021-03-22 15:15:35 +01:00
Daniel Gultsch 8b90c1c498 port POSH code to OkHttp 2021-03-22 14:32:31 +01:00
Daniel Gultsch 70fc08314f remove unused method 2021-03-22 11:14:35 +01:00
Daniel Gultsch ce7f59a76c use okhttp to fetch captcha 2021-03-22 10:39:53 +01:00
Daniel Gultsch 1cd95aefa6 migrate redirection urls to HttpUrl 2021-03-22 10:12:53 +01:00
Daniel Gultsch 739d20428a optimize imports 2021-03-21 21:39:04 +01:00
Daniel Gultsch 6ee2807027 bump gradle plugin 2021-03-21 21:18:33 +01:00
Daniel Gultsch a6244d986a use settable futures for slot requester 2021-03-21 20:45:26 +01:00
Daniel Gultsch 8ac97b0027 disable extmap_allow_mixed by default 2021-03-21 19:40:52 +01:00
Daniel Gultsch 7a115cb967 pulled translations from transifex 2021-03-21 19:36:21 +01:00
Daniel Gultsch d1195d21ae pulled translations from transifex 2021-03-20 11:58:43 +01:00
Daniel Gultsch 72828c6c4e fix 'checking file size' status display 2021-03-20 11:21:48 +01:00
Daniel Gultsch 38ef69a926 do not display toast for cancelled downloads 2021-03-20 11:00:20 +01:00
Daniel Gultsch aaac8296b3 only overwrite body in text messages 2021-03-19 19:51:13 +01:00
Daniel Gultsch e217551a82 migrate to OkHttp instead of HttpUrlConnection
OkHttp gives us more fine grained control over the HTTP library and frees us from any platform bugs
2021-03-19 14:57:20 +01:00
Daniel Gultsch b09a1432a3 Stanza.getErrorCondation only ever needs the tag name 2021-03-18 11:35:41 +01:00
Geno f161a9c4bf metadata: add changelog 2.9.8 2021-03-17 10:18:53 +01:00
Geno f8e69f9fc2 Merge tag '2.9.8' into develop 2021-03-17 10:17:05 +01:00
Daniel Gultsch 6f1b71970d parse extmap-allow-mixed 2021-03-16 18:52:38 +01:00
Daniel Gultsch 3baacf8862 switch to unified plan 2021-03-16 18:52:38 +01:00
Daniel Gultsch 9c2da0a1b8 upgrade libwebrtc to m89 2021-03-16 18:52:38 +01:00
Daniel Gultsch 2681ad82e1 complain if mLineIndex can not be found when receiving candidates 2021-03-16 18:52:25 +01:00
Daniel Gultsch 8764d11cce kill pending queries when archiving conversation 2021-03-16 10:22:52 +01:00
Daniel Gultsch 3c60de54cb minor code clean up 2021-03-16 08:16:07 +01:00
Daniel Gultsch d30a08266a
Fix typo in readme 2021-03-15 06:51:00 +00:00
Daniel Gultsch 859f3b2a1d fix NPE after race condition. fixes #4033 2021-03-13 11:13:19 +01:00
Daniel Gultsch bf25b24967 modify away when locked behaviour to locked || screen off
this new behaviour still takes care of not going online when quickly
checking for the time but it also includes systems that don’t have a
lock screen or incorrectly report being unlocked.
2021-03-13 10:52:06 +01:00
Daniel Gultsch 7c53dcc4f4 fixed NPE when service isnt bound 2021-03-13 10:51:41 +01:00
Daniel Gultsch bf9d1a5759 remove docs folder
* mission statement is now hosted on https://gultsch.de
* XEPs.md has been replaced by conversations.doap
* observations.md is just outdated and boring by now
2021-03-12 18:31:11 +01:00
Daniel Gultsch ddb54bb222 version bump to 2.9.8 + changelog 2021-03-11 10:37:29 +01:00
Daniel Gultsch 813b07e18d pulled translations from transifex 2021-03-09 09:58:33 +01:00
Daniel Gultsch 0fa06d65b5 overwrite body for plaintext group chat messages only 2021-03-07 09:59:09 +01:00
Daniel Gultsch e947a3f808 modify boyy on muc reflection 2021-03-06 12:43:59 +01:00
Daniel Gultsch b34f6e0720 null check weak reference value 2021-03-06 09:45:42 +01:00
Daniel Gultsch b8c61b795e use different top margin for vert and land for duration display 2021-03-06 09:45:13 +01:00
Daniel Gultsch cf68c544aa pulled translations from transifex 2021-03-06 08:58:28 +01:00
Christopher Vollick ef24d2050b Remove Renomination from WebRTC Options
This is a feature of WebRTC that's [not standardized][1] and only
supported by libwebrtc. Since there's no support in jingle for passing
this capability from one peer to another, we're currently hard-coding
this option into both the local candidate and also the remote candidate
so they can use it.

But I'm trying to call a user that isn't using WebRTC, and renomination
is causing the call to stay in "connecting..." state for 10 or 20
seconds, sometimes longer, while both sides wait for the other to
nominate something based on their individual beliefs about the standards
they're using.

Removing this seems to make connecting relatively instantaneous.

If we want to reintroduce this feature, we should probably make a XEP so
the peers can negotiate honestly about it, and only use it if both sides
truely support the feature.

[1]: https://datatracker.ietf.org/doc/html/draft-thatcher-ice-renomination-01
2021-03-04 08:26:52 +00:00
Daniel Gultsch 4a175f915d version bump to 2.9.8-beta 2021-03-04 09:25:20 +01:00
Daniel Gultsch 5848013a1e handle pre key messages in dtls verification 2021-03-03 14:03:08 +01:00
Daniel Gultsch c5f801c1fe do not push empty candidates to backlog 2021-03-03 13:12:10 +01:00
Daniel Gultsch d52c46d582 use omemo verification only if omemo is enabled in conversation 2021-03-03 12:55:27 +01:00
Daniel Gultsch e81fb1b24e pulled translations from transifex 2021-03-03 09:48:29 +01:00
Daniel Gultsch 3ee70b1d48 show verified shield in rtp session activity 2021-03-03 09:41:05 +01:00
Daniel Gultsch e4b2bb4a42 throw exception when unable to encrypt 2021-03-03 08:22:21 +01:00
Daniel Gultsch 8a6430ae29 ground work for omemo dtls verification 2021-03-02 21:13:49 +01:00
Geno a213b00091 Merge tag '2.9.7' into develop 2021-02-26 15:46:02 +01:00
Geno 64e88d946f fix crashes with internal database error 2021-02-26 14:46:41 +01:00
Daniel Gultsch 47a904b4fc pulled translations from transifex 2021-02-26 10:18:59 +01:00
Daniel Gultsch 33e73a2b47 bump version code for release 2021-02-24 12:18:14 +01:00
Daniel Gultsch d889c02a0a make ascii armor parsing more resiliant 2021-02-24 11:05:11 +01:00
Daniel Gultsch 073b6a998a pulled translations from transifex 2021-02-24 10:35:46 +01:00
Daniel Gultsch 9450d49b0b do not vibrate when in DND mode 2021-02-24 10:35:33 +01:00
Daniel Gultsch 5e0c158cde fix default ringtone for incoming call setting 2021-02-22 13:59:07 +01:00
Daniel Gultsch 16b1c561d4 pulled translations from transifex 2021-02-22 09:31:13 +01:00
Daniel Gultsch 24f2f52512 limit http upload / download to 4 parallel connections 2021-02-22 09:24:41 +01:00
Daniel Gultsch e98ec40b7f pulled translations from transifex 2021-02-21 14:15:33 +01:00
Daniel Gultsch f92ea5c70b resend <propose/> only if server has stream mgmt 2021-02-21 13:37:08 +01:00
Daniel Gultsch 81505c6202 version bump to 2.9.7-beta + changelog 2021-02-20 11:07:02 +01:00
Daniel Gultsch 638f30b902 pulled translations from transifex 2021-02-20 10:16:56 +01:00
Daniel Gultsch 0812bae1ab do not run alpha check on jpegs 2021-02-20 10:08:43 +01:00
Daniel Gultsch 6bfe16f044 replace away when screen off with away when locked
fixes #3978
2021-02-19 15:59:56 +01:00
Daniel Gultsch 53da64b7e2 do not attempt to play 'none' ringtone
trying to play 'none' ringtone resulted in the default ring tone being played
2021-02-19 15:31:01 +01:00
Daniel Gultsch ebb38d7d75 consume volume down event 2021-02-18 22:16:28 +01:00
Daniel Gultsch 484f633180 let Conversations (not Android) play ringtone and vibration
fixes #3972 fixes #3801 fixes #3931
2021-02-18 20:55:31 +01:00
Daniel Gultsch 78c89664c4 moved translations into new resource on transifex
the 'strings' resource on transifex was in the internal 'Android 1' format
instead of the more modern 'Android 2' format.

This according to transifex support caused some weird issues…

The only work around (apparently) was to create a new resource (now call
main-strings) and use that instead.

I hope we didn’t mess anything up in the process.

Let's be extra careful with the next release
2021-02-18 16:56:59 +01:00
Daniel Gultsch 90270069da pulled translations from transifex 2021-02-18 11:05:02 +01:00
Daniel Gultsch 49992f300b repharse omemo fingerprint description 2021-02-18 10:35:10 +01:00
Daniel Gultsch 72e268e6b1 add TODO comments wrt to missing <retract/> parsing 2021-02-18 09:36:51 +01:00
Daniel Gultsch 78901e3339 use detached signatures 2021-02-17 22:47:40 +01:00
Daniel Gultsch 149224a073 do not deduplicate disco queries
Conversations used to deduplicate disco queries based on their hash.
However that relies on the first query to go through (device to actually
respond) and to respond properly (hash matches).

Creating a proper retry behaviour for this is actually quite challanging.
(which one would you try next, how long do you wait?)
2021-02-17 18:14:18 +01:00
Daniel Gultsch db447f845e resend session proposal on rebind 2021-02-12 11:36:44 +01:00
Daniel Gultsch 6cab0ad496 make rtp proposal tracked by SM. fixes #3983 2021-02-12 10:35:13 +01:00
Daniel Gultsch 9f869d3895 slightly change wording of sync_with_contacts_long 2021-02-11 18:55:27 +01:00
Daniel Gultsch b808a03702 update dependencies
now that we have minSdk=21 we can bump retrofit and okhttp
2021-02-11 18:28:48 +01:00
Daniel Gultsch 7330d8a7f0 fixed race conditions around PROCEED state. fixes #3989 2021-02-11 16:56:57 +01:00
Daniel Gultsch 6e3dc0eef6 fix up for last commit. (re-add \n) 2021-02-11 09:05:29 +01:00
Geno 696f791dca release 2.9.6.1 : fix crashes with internal database error 2021-02-10 21:00:45 +01:00
Geno e101bd8ba2 fix sum7 translation 2021-02-10 21:00:45 +01:00
Daniel Gultsch 3847ab8465 paramaterize app name in more places 2021-02-10 15:56:02 +01:00
Daniel Gultsch deee31e517 pulled translations from transifex 2021-02-10 14:29:36 +01:00
mimi89999 ab17f935c3 Fix formatting of openkeychain_required_long message 2021-02-10 13:16:56 +00:00
mimi89999 d51b4380d7 Add variable app name in res strings
Closes #3988
2021-02-10 11:35:49 +00:00
Daniel Gultsch b6d62c13ef use ascii notation for punycode domains in SNI 2021-02-07 09:38:55 +01:00
Daniel Gultsch b76b60df5c verify against IDN variant of domain 2021-02-04 11:15:59 +01:00
Daniel Gultsch f82ae0a9b8 Merge branch 'maxim432-fix_leaks' 2021-01-31 10:13:35 +01:00
Daniel Gultsch 358c70828f close inputstream in image meta data analysers 2021-01-31 10:13:20 +01:00
maxim432 156c4da2b3 Fix couple of leaks 2021-01-30 17:56:54 -08:00
Daniel Gultsch ca496fd39f look at only subset of pixels to check for alpha 2021-01-30 01:50:03 +01:00
Daniel Gultsch 53a038d90e fix rtp offline discovery 2021-01-30 01:47:03 +01:00
Daniel Gultsch d77d89b356 mention in changelog that Android 5 is required 2021-01-30 00:37:09 +01:00
Daniel Gultsch 2155a50875 do not compress images with alpha channels 2021-01-29 21:25:00 +01:00
Daniel Gultsch 4a9dfb9567 fix copy url to clipboard action for undownloaded files 2021-01-28 17:49:34 +01:00
Daniel Gultsch 10382e83bf remove unused methods 2021-01-28 17:40:58 +01:00
Daniel Gultsch c11ac40df4 use 'missed call' as label for missed call status 2021-01-28 11:00:27 +01:00
Geno 3c8a5d138a Merge tag '2.9.6' into develop 2021-01-27 14:00:45 +01:00
Daniel Gultsch aee37c3e3e bump version code for release 2021-01-26 09:35:21 +01:00
Daniel Gultsch 0a2c753620 do not use offline fallback rtp capability if account is disabled 2021-01-26 09:35:03 +01:00
Daniel Gultsch d907d590d9 pulled translations from transifex 2021-01-26 08:39:26 +01:00
Daniel Gultsch 8e57a7622c use DayNight theme for splas screen
it doesn’t seem to be possible to make the splash screen use the configured
theme but we can follow the system’s theme on supported Android versions
2021-01-26 08:34:45 +01:00
Daniel Gultsch 8abf861303 version bump to 2.9.6 + changelog 2021-01-25 10:07:42 +01:00
Daniel Gultsch 247998ff81 add libwebrtc to library list 2021-01-24 08:38:56 +01:00
Daniel Gultsch 1068f16473 time flies 2021-01-24 08:29:38 +01:00
Daniel Gultsch 15a33adaae pulled translations from transifex 2021-01-24 08:27:27 +01:00
Daniel Gultsch 619af9c6c5 back button should not end call if call is connected. fixes #3975 2021-01-23 18:42:34 +01:00
Daniel Gultsch bc97d0b0bb pulled translations from transifex 2021-01-23 11:43:55 +01:00
Daniel Gultsch 9c9a953281 pluralize x_unread_conversations string 2021-01-23 11:25:22 +01:00
Daniel Gultsch 6a89a472e1 bump gradle plugin version 2021-01-23 10:09:34 +01:00
Daniel Gultsch 8ce7bfb95e automated code clean up 2021-01-23 09:25:34 +01:00
Daniel Gultsch 582aee4718 do not use empty display name for /me command 2021-01-22 12:51:01 +01:00
Daniel Gultsch 3b43cb0bda do not offer share button when file hasnt been downloaded. fixes #3971 2021-01-22 08:24:19 +01:00
Daniel Gultsch e711b3d294 remember last rtp capability 2021-01-22 08:24:19 +01:00
Daniel Gultsch e087b594ff do not include own phone number in sycn
fixes #3960
2021-01-22 08:24:19 +01:00
Daniel Gultsch b37f25335c version bump to 2.9.5 2021-01-22 08:24:19 +01:00
Daniel Gultsch 9c087b9dd4 pulled translations from transifex 2021-01-22 08:24:19 +01:00
Molly Miller ebbf1ff6b1 Use the account's display name (if set) when rendering /me messages in one-to-one chats. 2021-01-22 07:23:58 +00:00
Daniel Gultsch 20d0206859 pulled translations from transifex 2021-01-20 08:23:24 +01:00
Daniel Gultsch 9db0808306 show self contact with display name 2021-01-20 08:15:06 +01:00
Daniel Gultsch eea484af01 move SMS receiver into its own BroadcastReceiver 2021-01-20 08:14:36 +01:00
Daniel Gultsch 47d619b28e Quicksy: accept http status code 201 for account creation 2021-01-19 22:26:27 +01:00
Daniel Gultsch 60b23882fd fixed typo in travis file 2021-01-19 16:34:50 +01:00
Daniel Gultsch 47adf646c6 travis specificy all free build flavors 2021-01-19 16:03:11 +01:00
Daniel Gultsch 26a4598f3c automatically receive Quicksy SMS. fixes #3962
requires new version of QuicksyServer
2021-01-19 15:45:43 +01:00
Daniel Gultsch 624bb565a8 code clean up in tos activity 2021-01-19 13:53:47 +01:00
Daniel Gultsch 87e9b8037e request input focus in VerifyActivity 2021-01-19 13:31:49 +01:00
Daniel Gultsch 49d0558da1 fix tab height in StartConversation screen 2021-01-19 12:58:05 +01:00
Daniel Gultsch 6e5240fe2e more beans 2021-01-19 09:17:59 +01:00
Daniel Gultsch 260b203ea3 Revert "just xmpp things"
This reverts commit d0a2f1f45f.
2021-01-19 08:55:22 +01:00
Daniel Gultsch 864b0b3b3d Merge branch 'BlauerHunger-androidx_port' 2021-01-19 08:54:41 +01:00
Daniel Gultsch 91cc8f5011 bump various libraries that have been waiting for androidx 2021-01-18 21:49:31 +01:00
Ferdinand Pöll 453ca7c0ed Migrate from Android Support Library to AndroidX
Unignored gradle.properties since androidX requires additions there
See also https://developer.android.com/jetpack/androidx/migrate
2021-01-18 20:49:35 +01:00
Daniel Gultsch 5fd0700daa added XEPs for a/v calls to doap file 2021-01-18 09:32:36 +01:00
Daniel Gultsch 55fd0b8504 pulled translations from transifex 2021-01-18 09:32:20 +01:00
Geno d2cd482a07 Merge tag '2.9.4' into develop 2021-01-17 23:28:55 +01:00
Daniel Gultsch bfccfba00e fix in call notification being shown twice 2021-01-15 13:03:54 +01:00
Daniel Gultsch 5b48b4027e code clean up 2021-01-15 10:45:03 +01:00
Daniel Gultsch 41c045d779 pulled translations from transifex 2021-01-11 11:30:53 +01:00
Daniel Gultsch 07cc5c13ca version bump to 2.9.4 + changelog 2021-01-11 11:30:11 +01:00
Daniel Gultsch b0584137b4 pulled translations from transifex 2021-01-07 09:12:11 +01:00
Daniel Gultsch d0a2f1f45f just xmpp things 2021-01-06 11:46:09 +01:00
Daniel Gultsch 372ddbfb49 Revert "offline presences aborts session proposals. fixes #3943"
This reverts commit f23016c967.
2021-01-06 09:03:42 +01:00
Emmanuel Gil Peyrot 17c697eed9 add 'id' attribute to outgoing ICE-UDP candidates
this attribute is mandatory as per the XEP.
2021-01-03 16:32:28 +00:00
Daniel Gultsch 69dca53bf3 use libwebrtc-m87 2021-01-03 16:17:33 +01:00
Daniel Gultsch 6c2f0d29d8 use svg logo in doap file 2021-01-03 16:10:51 +01:00
Daniel Gultsch 2bec5459c5 properly null check ufrag and pwd before whitespace checking. fixes #3956 2021-01-03 16:05:17 +01:00
Daniel Gultsch 8eb685a7eb pulled translations from transifex 2021-01-02 09:09:23 +01:00
Daniel Gultsch 6d13ee52f0 version bump to 2.9.3 + changelog 2020-12-31 10:49:29 +01:00
Daniel Gultsch adb5a2b2c2 pulled translations from transifex 2020-12-31 10:41:14 +01:00
Daniel Gultsch 0569febf67 minor code clean up in XmppConnection class 2020-12-31 10:27:06 +01:00
Daniel Gultsch 0e54d8a2cf implement SCRAM-SHA512 2020-12-31 09:32:05 +01:00
Daniel Gultsch 2a57c92f63 rewrote scram cache implementation 2020-12-30 22:01:08 +01:00
Daniel Gultsch 692ee6c9fb SCRAM remove cache. made digest and hmac non static
DIGEST and HMAC were static variables. Those are initialized by
what ever concrete implementation gets executed first.

(Perform SCRAM-SHA1 first and those variables got initialized with
SHA1 variants)

For subsequent SHA256 executions those variables contained wrong
values.
2020-12-30 15:57:44 +01:00
Geno 1257ad8509 metadata: add changelog 2.9.2 2020-12-24 13:41:16 +01:00
Geno 1a0b4b441d Merge tag '2.9.2' into develop 2020-12-24 13:39:02 +01:00
Daniel Gultsch f23016c967 offline presences aborts session proposals. fixes #3943 2020-12-22 17:50:26 +01:00
Daniel Gultsch b4db2e5284 make ongoing call check null safe. fixes #3951 2020-12-22 14:30:54 +01:00
Daniel Gultsch 39229c34f6 cancel touch event after opening context menu in search view 2020-12-18 21:18:09 +01:00
Daniel Gultsch 090b3b18d0 don’t check for inRoster when doing jingle with oneself. fixes #3947 2020-12-11 14:25:56 +01:00
Daniel Gultsch d1490673bb work around race condition after opening easy invite dialog 2020-12-11 11:29:23 +01:00
Daniel Gultsch e38aa30a84 minor code clean up 2020-12-11 11:05:08 +01:00
Daniel Gultsch 00bc7a4b06 version bump to 2.9.2 + changelog 2020-12-10 19:32:40 +01:00
Daniel Gultsch f584179f2f store avatars in cache folder 2020-12-10 19:05:04 +01:00
Daniel Gultsch 07e965f8f3 pulled translations from transifex 2020-12-10 18:47:23 +01:00
Daniel Gultsch 9c67e8fec2 fix long pressing on a textview with link 2020-12-08 07:10:54 +01:00
Daniel Gultsch 849968107e remove unused CopyTextView
after removing the ability to select and copy text the transformation methods
provided by CopyTextView are no longer necessary.
2020-12-08 07:08:59 +01:00
Daniel Gultsch 7179d72f7e pulled translations from transifex 2020-12-07 16:14:25 +01:00
Daniel Gultsch 261207a4c0 ignore whitespace when trying to detect provisioning json
fixes #3940
2020-12-07 09:03:05 +01:00
Daniel Gultsch 303e205276 if file extension doesn’t exist. try to guess from content type. fixes #3939 2020-12-06 19:22:36 +01:00
Daniel Gultsch 99cb23fe14 share xmpp uri if landing url is not available 2020-12-06 17:57:55 +01:00
Daniel Gultsch 528f192f76 stop parsing random strings as xmpp addresses when scanning uris 2020-12-02 08:19:06 +01:00
Daniel Gultsch 778cfa846b implement stub easy onboarding activity for quicksy 2020-12-02 07:21:50 +01:00
Daniel Gultsch 00e1a93014 fixed typo in easy invites request code 2020-12-01 22:39:56 +01:00
Daniel Gultsch 1f392a688d initial (untested) support for easy onboarding invites 2020-12-01 20:31:30 +01:00
Daniel Gultsch 92083fec83 version bump to 2.9.1 2020-11-25 08:47:21 +01:00
Daniel Gultsch 35316ad93a pulled translations from transifex 2020-11-25 07:43:47 +01:00
Daniel Gultsch 3c7ac2524f update changelog file in preparation for 2.9.1 release 2020-11-16 12:54:40 +01:00
Daniel Gultsch 600f243797 pulled translations from transifex 2020-11-16 12:22:50 +01:00
Daniel Gultsch 36f5f77c30 disable autofill for password field when not in init mode
maybe fixes #3924
2020-11-16 12:08:10 +01:00
Daniel Gultsch c7ec6a9dae let media scanner scan backup file. fixes #3913
note that the ROMs I tested this on don’t require scanning for it
to appear on MTP. However it certainly don’t hurt either.
2020-11-15 10:43:21 +01:00
eta 0c563134da Enable the android:largeHeap flag
- With large accounts (such as mine), Conversations starts hitting up against
  the default heap limit pretty quickly, at which point it grinds to a halt as
  GC pause times increase.
- Furthermore, it's impossible to complete a backup with such an account, since
  Conversations will just run out of memory before the backup can complete.
- Enabling the `android:largeHeap` flag asks the OS for a bit more memory, which
  hopefully alleviates the problem for larger accounts.
2020-11-14 12:55:30 +00:00
Daniel Gultsch fc53271212 bump version to 2.9.1-beta 2020-11-14 12:01:43 +01:00
Daniel Gultsch 6485c77e09 pulled translations from transifex 2020-11-14 12:01:28 +01:00
Daniel Gultsch 1fafe4287d don’t send origin-ids to rooms that support stable-ids. fixes #3905 2020-11-14 11:59:35 +01:00
Daniel Gultsch 16c4e3eec7 keep conversation bold (unread) after responding to a call. fixes #3926 2020-11-14 09:55:43 +01:00
Daniel Gultsch afb2fb1326 use CoW data structure for read markers. fixes #3904 2020-11-13 20:37:32 +01:00
Daniel Gultsch 952387cb5a use lower margins on between in-call buttons on small displays
hopefully fixes #3890
2020-11-13 16:11:41 +01:00
Christoph Scholz 386b224123 fix escapeing in local channel discovery 2020-11-13 13:35:46 +00:00
Alexei Sorokin 501cdd5edf do not prepend asterisks before words in search
An asterisk is a special FTS4 operator when appended to a word but has no
special meaning when prepended.
2020-11-13 12:43:18 +00:00
Daniel Gultsch b327548c85 show toast if no application found to attach 2020-11-13 12:50:05 +01:00
Daniel Gultsch aaebb3a536 bump gradle and targetSdk 2020-11-13 12:24:42 +01:00
Daniel Gultsch 5ad054617b use instead of message id for receipt processing 2020-11-12 13:33:27 +01:00
Daniel Gultsch ff13cc2766 extracting chat state for chat with self should not cause markRead event. fixes #3906 2020-10-20 10:20:58 +02:00
Daniel Gultsch dc72bc5bc3 extend logging for not finding query 2020-10-08 20:21:53 +02:00
nico fdfac102e2 spelling
* various spelling fixes
2020-10-06 13:15:51 +00:00
eta 364502d1a3 Fix various memory leaks reported by LeakCanary
- In some places, we weren't nulling out references to destroyed objects. This
  fixes that.
- (These were all discovered via LeakCanary instrumentation, and the fixes are
  hopefully rather straightforward-looking.)
2020-10-05 11:33:29 +00:00
eta b4805ac2c5 Remove the ListSelectionManager / message body selection (fixes memory leak!)
- When the `viewHolder.messageBody` `TextView` created by a `MessageAdapter` is
  set to selectable, it leaks an `android.widget.Editor` (because that editor
  registers a view observer that never gets unregistered).
  - This memory leak is really quite problematic, as the message adapter is used
    a lot!
- Having the text be selectable is useless anyway, though; there isn't any way
  to select it (because long pressing just opens the context menu anyway).
  - It looks like the ListSelectionManager was meant to track selections across
    multiple messages. However, I'm not sure this feature ever gets used.
- Accordingly, this commit removes the entire feature, thus fixing the memory
  leak (since no `Editor` objects are ever created).
  - It should also reduce memory usage in general, since we aren't attaching an
    `Editor` to every single textview we create.
  - A `TextView` only allocates an `Editor` if you ask it to do certain things,
    like make the text selectable or register custom selection callbacks.
2020-10-05 11:33:12 +00:00
genofire 571cb5dd44 metadata: add changelog 2.9.0 2020-09-11 16:56:50 +02:00
genofire 06b2043b24 Merge commit 'v2.9.0' into develop 2020-09-11 16:53:56 +02:00
Daniel Gultsch afffe01868 add changelog for fastlane 2020-09-11 15:57:34 +02:00
Daniel Gultsch 0b4d12782b version bump to 2.9.0 2020-09-09 13:52:43 +02:00
Daniel Gultsch ce1b707837 pulled translations from transifex 2020-09-09 10:47:48 +02:00
Daniel Gultsch 7fb617e39a disable 'leave before join'
leaving a MUC before joining it was a work around for servers that did not treat a
<x/> join as a full join and didn’t send the full user list if they thought the user was
still in the room.
this happens if Conversations restarts after an inproper disconnect. The MUC will think
the user is still in the room.

however nowadays most modern servers will treat <x/> joins as full joins. on the user hand
leave before join would trigger flood prevention on ejabberds and race the first message
with the actual join (making the message arrive before the user is considered in the room)
2020-09-02 10:14:02 +02:00
Daniel Gultsch 9db0c85cda rename 'add to favorites' to 'pin to top' 2020-09-02 10:13:10 +02:00
Daniel Gultsch 27c89e487a restructure conversation menu
we don’t want 'manage accounts' and 'settings' to show up when within a conversation.
we also move out disable notifications and add to favorites into an overflow overflow
to make the menu shorter (after adding 'Search messages' it became very crowded)
2020-09-01 16:50:28 +02:00
Daniel Gultsch 73dac680e5 show notification if message failed to deliver. closes #3540 2020-09-01 14:04:38 +02:00
Daniel Gultsch 23ed0ce2ad upgrade libwebrtc to m85. fixes #3870 2020-09-01 11:42:00 +02:00
Daniel Gultsch aa792a3af6 pulled translations from transifex 2020-09-01 11:41:16 +02:00
Daniel Gultsch 8d64e101c9 provide content description for fab in start conversation 2020-08-31 18:06:24 +02:00
Daniel Gultsch 3281a93dc3 use content description for play/pause button in audio player 2020-08-31 17:42:03 +02:00
Daniel Gultsch 81985ca7d7 shorten 'close conversation' string 2020-08-31 17:33:37 +02:00
Daniel Gultsch f76ef17494 provide content descriptions for call screen 2020-08-31 17:30:54 +02:00
Daniel Gultsch 4b12033bd3 pulled translations from transifex 2020-08-31 17:12:41 +02:00
Daniel Gultsch ce81123112 use content description in search fields 2020-08-31 17:12:31 +02:00
Daniel Gultsch 2de8f3d35b change title of lock icon when changing encryption 2020-08-31 16:56:23 +02:00
Daniel Gultsch 97fe9fa01f parse error messages from MAM results 2020-08-31 14:38:48 +02:00
Daniel Gultsch 6590dc922f homogenize accessibility traversal in sent and received messages 2020-08-31 13:20:17 +02:00
Daniel Gultsch c48499253b set content description for all avatars 2020-08-31 13:05:10 +02:00
Daniel Gultsch 3c0773c6e7 use darker accent color in light theme 2020-08-31 11:19:27 +02:00
Daniel Gultsch 064264c20b parse 'received' carbon-copied error messages. fixes #3803 2020-08-31 11:06:26 +02:00
Daniel Gultsch 3dcb36a417 persist presence name (pep, nick in subscribe) to DB. fixes #3856 2020-08-31 09:03:54 +02:00
Daniel Gultsch 35af8894d2 search individual conversations. fixes #3243 2020-08-29 08:16:08 +02:00
genofire 2cd0993534 release 2.8.10.1 with security fix
update WebRTC to Version 1.0.32006 / M86
Vulnerability: https://googleprojectzero.blogspot.com/2020/08/exploiting-android-messengers-part-3.html
2020-08-28 17:31:24 +02:00
genofire 35e064949d build.grandle: update webrtc lib 2020-08-28 12:10:02 +02:00
Daniel Gultsch d158eeaf72 terminate jingle call when regular call starts 2020-08-24 12:47:54 +02:00
Daniel Gultsch 91e94db747 extend isBusyState to check phone state as well 2020-08-24 09:51:26 +02:00
Daniel Gultsch 15b323ee69 fix crash after session-accept failed and session-accept contained candidates
Conversations would attempt to feed any candidates found in the session-accept into
WebRTC; even if the session wasn’t setup correctly.

this commit processes the candidates only if the session was setup correctly

fixes #3867
2020-08-22 08:12:28 +02:00
Daniel Gultsch f3362ebde5 add start/install orbot to error notification if applicable. fixes #3846 2020-08-19 15:29:25 +02:00
Daniel Gultsch 70c10fd0de listen to orbot events instead of using intent result to reconnect account 2020-08-19 13:57:33 +02:00
Daniel Gultsch 1958cded23 improve logging for app server failures. change wording to include 'push' 2020-08-19 13:18:27 +02:00
Daniel Gultsch 7d2a7d536d fix attachments getting lost when switching to chat during call
fixes #3854
2020-08-05 21:31:56 +02:00
474 changed files with 38086 additions and 30146 deletions

38
.github/workflows/android.yml vendored Normal file
View File

@ -0,0 +1,38 @@
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: 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)
run: ./gradlew assembleQuicksyFreeCompatDebug
- name: Build Quicksy (System)
run: ./gradlew assembleQuicksyFreeSystemDebug
- name: Build Conversations (Compat)
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

1
.gitignore vendored
View File

@ -9,7 +9,6 @@ src/quicksyPlaystore/res/values/push.xml
# https://github.com/github/gitignore/blob/master/Gradle.gitignore # https://github.com/github/gitignore/blob/master/Gradle.gitignore
.gradle/ .gradle/
build/ build/
gradle.properties
captures/ captures/
signing.properties signing.properties
# Ignore Gradle GUI config # Ignore Gradle GUI config

View File

@ -1,20 +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-m84.aar http://gultsch.de/files/libwebrtc-m84.aar
script:
- ./gradlew assembleConversationsFreeSystemRelease
- ./gradlew assembleQuicksyFreeCompatRelease
before_install:
- yes | sdkmanager "platforms;android-28"

View File

@ -2,7 +2,7 @@
host = https://www.transifex.com host = https://www.transifex.com
lang_map = af_ZA: af-rZA, am_ET: am-rET, ar_AE: ar-rAE, ar_BH: ar-rBH, ar_DZ: ar-rDZ, ar_EG: ar-rEG, ar_IQ: ar-rIQ, ar_JO: ar-rJO, ar_KW: ar-rKW, ar_LB: ar-rLB, ar_LY: ar-rLY, ar_MA: ar-rMA, ar_OM: ar-rOM, ar_QA: ar-rQA, ar_SA: ar-rSA, ar_SY: ar-rSY, ar_TN: ar-rTN, ar_YE: ar-rYE, arn_CL: arn-rCL, as_IN: as-rIN, az_AZ: az-rAZ, ba_RU: ba-rRU, be_BY: be-rBY, bg_BG: bg-rBG, bn_BD: bn-rBD, bn_IN: bn-rIN, bo_CN: bo-rCN, br_FR: br-rFR, bs_BA: bs-rBA, ca_ES: ca-rES, co_FR: co-rFR, cs_CZ: cs-rCZ, cy_GB: cy-rGB, da_DK: da-rDK, de_AT: de-rAT, de_CH: de-rCH, de_DE: de-rDE, de_LI: de-rLI, de_LU: de-rLU, dsb_DE: dsb-rDE, dv_MV: dv-rMV, el_GR: el-rGR, en_AU: en-rAU, en_BZ: en-rBZ, en_CA: en-rCA, en_GB: en-rGB, en_IE: en-rIE, en_IN: en-rIN, en_JM: en-rJM, en_MY: en-rMY, en_NZ: en-rNZ, en_PH: en-rPH, en_SG: en-rSG, en_TT: en-rTT, en_US: en-rUS, en_ZA: en-rZA, en_ZW: en-rZW, es_AR: es-rAR, es_BO: es-rBO, es_CL: es-rCL, es_CO: es-rCO, es_CR: es-rCR, es_DO: es-rDO, es_EC: es-rEC, es_ES: es-rES, es_GT: es-rGT, es_HN: es-rHN, es_MX: es-rMX, es_NI: es-rNI, es_PA: es-rPA, es_PE: es-rPE, es_PR: es-rPR, es_PY: es-rPY, es_SV: es-rSV, es_US: es-rUS, es_UY: es-rUY, es_VE: es-rVE, et_EE: et-rEE, eu_ES: eu-rES, fa_IR: fa-rIR, fi_FI: fi-rFI, fil_PH: fil-rPH, fo_FO: fo-rFO, fr_BE: fr-rBE, fr_CA: fr-rCA, fr_CH: fr-rCH, fr_FR: fr-rFR, fr_LU: fr-rLU, fr_MC: fr-rMC, fy_NL: fy-rNL, ga_IE: ga-rIE, gd_GB: gd-rGB, gl_ES: gl-rES, gsw_FR: gsw-rFR, gu_IN: gu-rIN, ha_NG: ha-rNG, hi_IN: hi-rIN, hr_BA: hr-rBA, hr_HR: hr-rHR, hsb_DE: hsb-rDE, hu_HU: hu-rHU, hy_AM: hy-rAM, id_ID: id-rID, ig_NG: ig-rNG, ii_CN: ii-rCN, is_IS: is-rIS, it_CH: it-rCH, it_IT: it-rIT, iu_CA: iu-rCA, ja_JP: ja-rJP, ka_GE: ka-rGE, kk_KZ: kk-rKZ, kl_GL: kl-rGL, km_KH: km-rKH, kn_IN: kn-rIN, ko_KR: ko-rKR, kok_IN: kok-rIN, ky_KG: ky-rKG, lb_LU: lb-rLU, lo_LA: lo-rLA, lt_LT: lt-rLT, lv_LV: lv-rLV, mi_NZ: mi-rNZ, mk_MK: mk-rMK, ml_IN: ml-rIN, mn_CN: mn-rCN, mn_MN: mn-rMN, moh_CA: moh-rCA, mr_IN: mr-rIN, ms_BN: ms-rBN, ms_MY: ms-rMY, mt_MT: mt-rMT, nb_NO: nb-rNO, ne_NP: ne-rNP, nl_BE: nl-rBE, nl_NL: nl-rNL, nn_NO: nn-rNO, nso_ZA: nso-rZA, oc_FR: oc-rFR, or_IN: or-rIN, pa_IN: pa-rIN, pl_PL: pl-rPL, prs_AF: prs-rAF, ps_AF: ps-rAF, pt_BR: pt-rBR, pt_PT: pt-rPT, qut_GT: qut-rGT, quz_BO: quz-rBO, quz_EC: quz-rEC, quz_PE: quz-rPE, rm_CH: rm-rCH, ro_RO: ro-rRO, ru_RU: ru-rRU, rw_RW: rw-rRW, sa_IN: sa-rIN, sah_RU: sah-rRU, se_FI: se-rFI, se_NO: se-rNO, se_SE: se-rSE, si_LK: si-rLK, sk_SK: sk-rSK, sl_SI: sl-rSI, sma_NO: sma-rNO, sma_SE: sma-rSE, smj_NO: smj-rNO, smj_SE: smj-rSE, smn_FI: smn-rFI, sms_FI: sms-rFI, sq_AL: sq-rAL, sr_BA: sr-rBA, sr_CS: sr-rCS, sr_ME: sr-rME, sr_RS: sr-rRS, sv_FI: sv-rFI, sv_SE: sv-rSE, sw_KE: sw-rKE, syr_SY: syr-rSY, ta_IN: ta-rIN, te_IN: te-rIN, tg_TJ: tg-rTJ, th_TH: th-rTH, tk_TM: tk-rTM, tn_ZA: tn-rZA, tr_TR: tr-rTR, tt_RU: tt-rRU, tzm_DZ: tzm-rDZ, ug_CN: ug-rCN, uk_UA: uk-rUA, ur_PK: ur-rPK, uz_UZ: uz-rUZ, vi_VN: vi-rVN, wo_SN: wo-rSN, xh_ZA: xh-rZA, yo_NG: yo-rNG, zh_CN: zh-rCN, zh_HK: zh-rHK, zh_MO: zh-rMO, zh_SG: zh-rSG, zh_TW: zh-rTW, zu_ZA: zu-rZA, no_NO: no-rNO, he_IL: iw-rIL, he: iw lang_map = af_ZA: af-rZA, am_ET: am-rET, ar_AE: ar-rAE, ar_BH: ar-rBH, ar_DZ: ar-rDZ, ar_EG: ar-rEG, ar_IQ: ar-rIQ, ar_JO: ar-rJO, ar_KW: ar-rKW, ar_LB: ar-rLB, ar_LY: ar-rLY, ar_MA: ar-rMA, ar_OM: ar-rOM, ar_QA: ar-rQA, ar_SA: ar-rSA, ar_SY: ar-rSY, ar_TN: ar-rTN, ar_YE: ar-rYE, arn_CL: arn-rCL, as_IN: as-rIN, az_AZ: az-rAZ, ba_RU: ba-rRU, be_BY: be-rBY, bg_BG: bg-rBG, bn_BD: bn-rBD, bn_IN: bn-rIN, bo_CN: bo-rCN, br_FR: br-rFR, bs_BA: bs-rBA, ca_ES: ca-rES, co_FR: co-rFR, cs_CZ: cs-rCZ, cy_GB: cy-rGB, da_DK: da-rDK, de_AT: de-rAT, de_CH: de-rCH, de_DE: de-rDE, de_LI: de-rLI, de_LU: de-rLU, dsb_DE: dsb-rDE, dv_MV: dv-rMV, el_GR: el-rGR, en_AU: en-rAU, en_BZ: en-rBZ, en_CA: en-rCA, en_GB: en-rGB, en_IE: en-rIE, en_IN: en-rIN, en_JM: en-rJM, en_MY: en-rMY, en_NZ: en-rNZ, en_PH: en-rPH, en_SG: en-rSG, en_TT: en-rTT, en_US: en-rUS, en_ZA: en-rZA, en_ZW: en-rZW, es_AR: es-rAR, es_BO: es-rBO, es_CL: es-rCL, es_CO: es-rCO, es_CR: es-rCR, es_DO: es-rDO, es_EC: es-rEC, es_ES: es-rES, es_GT: es-rGT, es_HN: es-rHN, es_MX: es-rMX, es_NI: es-rNI, es_PA: es-rPA, es_PE: es-rPE, es_PR: es-rPR, es_PY: es-rPY, es_SV: es-rSV, es_US: es-rUS, es_UY: es-rUY, es_VE: es-rVE, et_EE: et-rEE, eu_ES: eu-rES, fa_IR: fa-rIR, fi_FI: fi-rFI, fil_PH: fil-rPH, fo_FO: fo-rFO, fr_BE: fr-rBE, fr_CA: fr-rCA, fr_CH: fr-rCH, fr_FR: fr-rFR, fr_LU: fr-rLU, fr_MC: fr-rMC, fy_NL: fy-rNL, ga_IE: ga-rIE, gd_GB: gd-rGB, gl_ES: gl-rES, gsw_FR: gsw-rFR, gu_IN: gu-rIN, ha_NG: ha-rNG, hi_IN: hi-rIN, hr_BA: hr-rBA, hr_HR: hr-rHR, hsb_DE: hsb-rDE, hu_HU: hu-rHU, hy_AM: hy-rAM, id_ID: id-rID, ig_NG: ig-rNG, ii_CN: ii-rCN, is_IS: is-rIS, it_CH: it-rCH, it_IT: it-rIT, iu_CA: iu-rCA, ja_JP: ja-rJP, ka_GE: ka-rGE, kk_KZ: kk-rKZ, kl_GL: kl-rGL, km_KH: km-rKH, kn_IN: kn-rIN, ko_KR: ko-rKR, kok_IN: kok-rIN, ky_KG: ky-rKG, lb_LU: lb-rLU, lo_LA: lo-rLA, lt_LT: lt-rLT, lv_LV: lv-rLV, mi_NZ: mi-rNZ, mk_MK: mk-rMK, ml_IN: ml-rIN, mn_CN: mn-rCN, mn_MN: mn-rMN, moh_CA: moh-rCA, mr_IN: mr-rIN, ms_BN: ms-rBN, ms_MY: ms-rMY, mt_MT: mt-rMT, nb_NO: nb-rNO, ne_NP: ne-rNP, nl_BE: nl-rBE, nl_NL: nl-rNL, nn_NO: nn-rNO, nso_ZA: nso-rZA, oc_FR: oc-rFR, or_IN: or-rIN, pa_IN: pa-rIN, pl_PL: pl-rPL, prs_AF: prs-rAF, ps_AF: ps-rAF, pt_BR: pt-rBR, pt_PT: pt-rPT, qut_GT: qut-rGT, quz_BO: quz-rBO, quz_EC: quz-rEC, quz_PE: quz-rPE, rm_CH: rm-rCH, ro_RO: ro-rRO, ru_RU: ru-rRU, rw_RW: rw-rRW, sa_IN: sa-rIN, sah_RU: sah-rRU, se_FI: se-rFI, se_NO: se-rNO, se_SE: se-rSE, si_LK: si-rLK, sk_SK: sk-rSK, sl_SI: sl-rSI, sma_NO: sma-rNO, sma_SE: sma-rSE, smj_NO: smj-rNO, smj_SE: smj-rSE, smn_FI: smn-rFI, sms_FI: sms-rFI, sq_AL: sq-rAL, sr_BA: sr-rBA, sr_CS: sr-rCS, sr_ME: sr-rME, sr_RS: sr-rRS, sv_FI: sv-rFI, sv_SE: sv-rSE, sw_KE: sw-rKE, syr_SY: syr-rSY, ta_IN: ta-rIN, te_IN: te-rIN, tg_TJ: tg-rTJ, th_TH: th-rTH, tk_TM: tk-rTM, tn_ZA: tn-rZA, tr_TR: tr-rTR, tt_RU: tt-rRU, tzm_DZ: tzm-rDZ, ug_CN: ug-rCN, uk_UA: uk-rUA, ur_PK: ur-rPK, uz_UZ: uz-rUZ, vi_VN: vi-rVN, wo_SN: wo-rSN, xh_ZA: xh-rZA, yo_NG: yo-rNG, zh_CN: zh-rCN, zh_HK: zh-rHK, zh_MO: zh-rMO, zh_SG: zh-rSG, zh_TW: zh-rTW, zu_ZA: zu-rZA, no_NO: no-rNO, he_IL: iw-rIL, he: iw
[conversations.strings] [conversations.main-strings]
file_filter = src/main/res/values-<lang>/strings.xml file_filter = src/main/res/values-<lang>/strings.xml
source_file = src/main/res/values/strings.xml source_file = src/main/res/values/strings.xml
source_lang = en source_lang = en

View File

@ -1,5 +1,92 @@
# Changelog # Changelog
### Version 2.10.2
* Fix crash when rendering some quotes
* Fix crash in welcome screen
### Version 2.10.1
* Fix issue with some videos not being compressed
* Fix rare crash when opening notification
### 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
* minor A/V improvements
### Version 2.9.12
* Always verify domain name. No user overwrite
* Support roster pre authentication
### Version 2.9.11
* Fixed 'No Connectivity' issues on Android 7.1
### Version 2.9.10
* fix HTTP up/download for users that dont trust system CAs
### Version 2.9.9
* Various bug fixes around Tor support
* Improve call compatibility with Dino
### Version 2.9.8
* Verify A/V calls with preexisting OMEMO sessions
* Improve compatibility with non libwebrtc WebRTC implementations
### Version 2.9.7
* Ability to select incoming call ringtone
* Fix OpenPGP key id discovery for OpenKeychain 5.6+
* Properly verify punycode TLS certificates
* Improve stability of RTP session establishment (calling)
### Version 2.9.6
* Show call button for offline contacts if they previously announced support
* Back button no longer ends call when call is connected
* bug fixes
### Version 2.9.5
* Quicksy: Automatically receive verification SMS
### Version 2.9.4
* minor stability improvements for A/V calls
* Conversations releases from here on forward require Android 5
### Version 2.9.3
* Fixed connectivity issues when different accounts used different SCRAM mechanisms
* Add support for SCRAM-SHA-512
* Allow P2P (Jingle) file transfer with self contact
### Version 2.9.2
* Offer Easy Invite generation on supporting servers
* Display GIFs send from Movim
* store avatars in cache
### Version 2.9.1
* fixed search on Android <= 5
* optimize memory consumption
### Version 2.9.0
* Search individual conversations
* Notify user if message delivery fails
* Remember display names (nicks) from Quicksy users across restarts
* Add button to start Orbot (Tor) from notification if necessary
### Version 2.8.10 ### Version 2.8.10
* Handle GPX files * Handle GPX files
@ -412,10 +499,9 @@
* Icons for attach menu * Icons for attach menu
### Version 1.16.2 ### Version 1.16.2
* change mam catchup strategie. support mam:1 * change mam catchup strategy. support mam:1
* bug fixes * bug fixes
### Version 1.16.1 ### Version 1.16.1
* UI performance fixes * UI performance fixes
* bug fixes * bug fixes
@ -465,7 +551,7 @@
* bug fixes * bug fixes
### Version 1.14.6 ### Version 1.14.6
* make error notification dismissable * make error notification dismissible
* bug fixes * bug fixes
@ -489,7 +575,7 @@
* bug fixes * bug fixes
### Version 1.14.0 ### Version 1.14.0
* Improvments for N * Improvements for N
* Quick Reply to Notifications on N * Quick Reply to Notifications on N
* Don't download avatars and files when data saver is on * Don't download avatars and files when data saver is on
* bug fixes * bug fixes
@ -667,7 +753,7 @@
### Version 1.7.0 ### Version 1.7.0
* CAPTCHA support * CAPTCHA support
* SASL EXTERNAL (client certifiates) * SASL EXTERNAL (client certificates)
* fetching MUC history via MAM * fetching MUC history via MAM
* redownload deleted files from HTTP hosts * redownload deleted files from HTTP hosts
* Expert setting to automatically set presence * Expert setting to automatically set presence
@ -775,7 +861,7 @@
* accept more ciphers * accept more ciphers
### Version 1.0 ### Version 1.0
* MUC controls (Affiliaton changes) * MUC controls (Affiliation changes)
* Added download button to notification * Added download button to notification
* Added check box to hide offline contacts * Added check box to hide offline contacts
* Use Material theme and icons on Android L * Use Material theme and icons on Android L
@ -881,7 +967,7 @@
* XEP-0333. Mark whether the other party has read your messages * XEP-0333. Mark whether the other party has read your messages
* Delayed messages are now tagged properly * Delayed messages are now tagged properly
* Share images from the Gallery * Share images from the Gallery
* Infinit history scrolling * Infinite history scrolling
* Mark the last used presence in presence selection dialog * Mark the last used presence in presence selection dialog
### Version 0.3 ### Version 0.3

View File

@ -43,7 +43,7 @@
* End-to-end encryption with [OMEMO](http://conversations.im/omemo/) or [OpenPGP](http://openpgp.org/about/) * End-to-end encryption with [OMEMO](http://conversations.im/omemo/) or [OpenPGP](http://openpgp.org/about/)
* Send and receive images as well as other kind of files * Send and receive images as well as other kind of files
* Encrypted audio and video calls (DTLS-SRTP) * [Encrypted audio and video calls (DTLS-SRTP)](https://help.conversations.im)
* Share your location * Share your location
* Send voice messages * Send voice messages
* Indication when your contact has read your message * Indication when your contact has read your message
@ -139,9 +139,9 @@ Note: This is kind of a weird quirk in OpenFire. Most other servers would just t
Maybe you attempted to use the Jabber ID `test@b.tld` because `a.tld` doesnt point to the correct host. In that case you might have to enable the extended connection settings in the expert settings of Conversations and set a host name. Maybe you attempted to use the Jabber ID `test@b.tld` because `a.tld` doesnt point to the correct host. In that case you might have to enable the extended connection settings in the expert settings of Conversations and set a host name.
### I get 'Stream opening error'. What does that mean? #### I get 'Stream opening error'. What does that mean?
In most cases this error is caused by ejabberd advertising support for TLSv1.3 but not properly supporting it. This can happen if the openssl version on the server already supports TLSv1.3 but the fast\_tls wrapper library used by ejabberd not (properly) support it. Upgrading fast\_tls and ejabberd or - theoretically - downgrading openssl should fix the issue. A work around is to explicity disable TLSv1.3 support in the ejabberd configuration. More information can be found on [this issue on the ejabberd issue tracker](https://github.com/processone/ejabberd/issues/2614). In most cases this error is caused by ejabberd advertising support for TLSv1.3 but not properly supporting it. This can happen if the OpenSSL version on the server already supports TLSv1.3 but the fast\_tls wrapper library used by ejabberd not (properly) support it. Upgrading fast\_tls and ejabberd or - theoretically - downgrading OpenSSL should fix the issue. A work around is to explicitly disable TLSv1.3 support in the ejabberd configuration. More information can be found on [this issue on the ejabberd issue tracker](https://github.com/processone/ejabberd/issues/2614).
#### Im getting this annoying permanent notification #### Im getting this annoying permanent notification
@ -149,7 +149,7 @@ Starting with Conversations 2.3.6 Conversations releases distributed over the Go
However you can disable the notification via settings of the operating system. (Not settings in Conversations.) However you can disable the notification via settings of the operating system. (Not settings in Conversations.)
**The battery consumption and the entire behaviour of Conversations will remain the same (as good or as bad as it was before). Why is Google doing this to you? We have no idea.** **The battery consumption and the entire behavior of Conversations will remain the same (as good or as bad as it was before). Why is Google doing this to you? We have no idea.**
##### Android &lt;= 7.1 or Conversations from F-Droid (all Android versions) ##### Android &lt;= 7.1 or Conversations from F-Droid (all Android versions)
The foreground notification is still controlled over the expert settings within Conversations as it always has been. Whether or not you need to enable it depends on how aggressive the non-standard 'power saving' features are that your phone vendor has built into the operating system. The foreground notification is still controlled over the expert settings within Conversations as it always has been. Whether or not you need to enable it depends on how aggressive the non-standard 'power saving' features are that your phone vendor has built into the operating system.
@ -173,7 +173,7 @@ You can find a detailed description of how your server, the app server and FCM a
#### But why do I need a permanent notification if I use Google Push? #### But why do I need a permanent notification if I use Google Push?
FCM (Google Push) allows an app to wake up from *Doze* which is (as the name suggests) a hibernation feature of the Android operating system that cuts the network connection and also reduces the number of times the app is allowed to wake up (to ping the server for example). The app can ask to be excluded from doze. Non push variants of the app (from F-Droid or if the server doesnt support it) will do this on first start up. So if you get exemption from *Doze*, or if you get regular push events sent to you, Doze should not pose a threat to Conversatons working properly. But even with *Doze* the app is still open in the background (kept in memory); it is just limited in the actions it can do. Conversations needs to stay in memory to hold certain session state (online status of contacts, join status of group chats, …). However with Android 8 Google changed all of this again and now an App that wants to stay in memory needs to have a foreground service which is visible to the user via the annoying notification. But why does Conversations need to hold that state? XMPP is a stateful protocol that has a lot of per-session information; packets need to be counted, presence information needs to be held, some features like Message Carbons get activated once per session, MAM catchup happens once, service discovery happens only once; the list goes on. When Conversations was created in early 2014 none of this was a problem because apps were just allowed to stay in memory. Basically every XMPP client out there holds that information in memory because it would be a lot more complicated trying to persist it to disk. An entire rewrite of Conversations in the year 2019 would attempt to do that and would probably succeed however it would require exactly that; a complete rewrite which is not feasible right now. Thats by the way also the reason why it is difficult to write an XMPP client on iOS. Or more broadly put this is also the reason why other protocols are designed as or migrated to stateless protocols (often based on HTTP); take for example the migration of IMAP to [JMAP](https://jmap.io/). FCM (Google Push) allows an app to wake up from *Doze* which is (as the name suggests) a hibernation feature of the Android operating system that cuts the network connection and also reduces the number of times the app is allowed to wake up (to ping the server for example). The app can ask to be excluded from doze. Non push variants of the app (from F-Droid or if the server doesnt support it) will do this on first start up. So if you get exemption from *Doze*, or if you get regular push events sent to you, Doze should not pose a threat to Conversatons working properly. But even with *Doze* the app is still open in the background (kept in memory); it is just limited in the actions it can do. Conversations needs to stay in memory to hold certain session state (online status of contacts, join status of group chats, …). However with Android 8 Google changed all of this again and now an App that wants to stay in memory needs to have a foreground service which is visible to the user via the annoying notification. But why does Conversations need to hold that state? XMPP is a statefull protocol that has a lot of per-session information; packets need to be counted, presence information needs to be held, some features like Message Carbons get activated once per session, MAM catch-up happens once, service discovery happens only once; the list goes on. When Conversations was created in early 2014 none of this was a problem because apps were just allowed to stay in memory. Basically every XMPP client out there holds that information in memory because it would be a lot more complicated trying to persist it to disk. An entire rewrite of Conversations in the year 2019 would attempt to do that and would probably succeed however it would require exactly that; a complete rewrite which is not feasible right now. Thats by the way also the reason why it is difficult to write an XMPP client on iOS. Or more broadly put this is also the reason why other protocols are designed as or migrated to stateless protocols (often based on HTTP); take for example the migration of IMAP to [JMAP](https://jmap.io/).
#### Conversations doesnt work for me. Where can I get help? #### Conversations doesnt work for me. Where can I get help?
@ -267,11 +267,11 @@ and introduce yourself to `iNPUTmice` so he can approve your join request.
#### How do I backup / move Conversations to a new device? #### How do I backup / move Conversations to a new device?
On the one hand Conversations supports Message Archive Management to keep a server side history of your messages so when migrating to a new device that device can display your entire history. However that does not work if you enable OMEMO due to its forward secrecy. (Read [The State of Mobile XMPP in 2016](https://gultsch.de/xmpp_2016.html) especially the section on encryption.) On the one hand Conversations supports Message Archive Management to keep a server side history of your messages so when migrating to a new device that device can display your entire history. However that does not work if you enable OMEMO due to its forward secrecy. (Read [The State of Mobile XMPP in 2016](https://gultsch.de/xmpp_2016.html) especially the section on encryption.)
As of version 2.4.0 an integrated Backup & Restore function will help with this, go to Settings and youll find a setting called Create backup. A notification will pop-up during the creation process that will announce you when it's ready. After the files, one for each account, are created, you can move the **Conversations** folder *(if you want your old media files too)* or only the **Conversations/Backup** folder *(for OMEMO keys and history only)* to your new device (or to a storage place) where a freshly installed Conversations can restore each account. Don't forget to enable the accounts after a succesful restore. As of version 2.4.0 an integrated Backup & Restore function will help with this, go to Settings and youll find a setting called Create backup. A notification will pop-up during the creation process that will announce you when it's ready. After the files, one for each account, are created, you can move the **Conversations** folder *(if you want your old media files too)* or only the **Conversations/Backup** folder *(for OMEMO keys and history only)* to your new device (or to a storage place) where a freshly installed Conversations can restore each account. Don't forget to enable the accounts after a successfull restore.
This backup method will include your OMEMO keys. Due to forward secrecy you will not be able to recover messages sent and received between creating the backup and restoring it. If you have a server side archive (MAM) those messages will be retrieved but displayed as *unable to decrypt*. For technical reasons you might also lose the first message you either sent or receive after the restore; for each conversation you have. This message will then also show up as *unable to decrypt*, but this will automatically recover itself as long as both participants are on Conversations 2.3.11+. Note that this doesnt happen if you just transfer to a new phone and no messages have been exchanged between backup and restore. This backup method will include your OMEMO keys. Due to forward secrecy you will not be able to recover messages sent and received between creating the backup and restoring it. If you have a server side archive (MAM) those messages will be retrieved but displayed as *unable to decrypt*. For technical reasons you might also lose the first message you either sent or receive after the restore; for each conversation you have. This message will then also show up as *unable to decrypt*, but this will automatically recover itself as long as both participants are on Conversations 2.3.11+. Note that this doesnt happen if you just transfer to a new phone and no messages have been exchanged between backup and restore.
In the vast, vast majority of cases you wont have to manually delete OMEMO keys or do anything like that. Conversations only introduced the offical backup feature in 2.4.0 after making sure the *OMEMO self healing* mechanism introduced in 2.3.11 works fine. In the vast, vast majority of cases you wont have to manually delete OMEMO keys or do anything like that. Conversations only introduced the official backup feature in 2.4.0 after making sure the *OMEMO self healing* mechanism introduced in 2.3.11 works fine.
**WARNING**: Be sure to know your accounts passwords or find ways to reset them **before** doing the backup as the files are encrypted using those passwords and the Restore process will ask for them. **WARNING**: Be sure to know your accounts passwords or find ways to reset them **before** doing the backup as the files are encrypted using those passwords and the Restore process will ask for them.
**WARNING**: Do not use the restore backup feature in an attempt to clone (run simultaneously) an installation. Restoring a backup is only meant for migrations or in case youve lost the original device. **WARNING**: Do not use the restore backup feature in an attempt to clone (run simultaneously) an installation. Restoring a backup is only meant for migrations or in case youve lost the original device.
@ -327,7 +327,7 @@ OMEMO has two requirements: Your server and the server of your contact need to s
OMEMO encryption works only in private (members only) conferences that are non-anonymous. Non-anonymous (being able to discover the real JID of other participants) is a technical requirement to discover the key material. Members only is a sort of arbitrary requirement imposed by Conversations. (see 'OMEMO is grayed out') OMEMO encryption works only in private (members only) conferences that are non-anonymous. Non-anonymous (being able to discover the real JID of other participants) is a technical requirement to discover the key material. Members only is a sort of arbitrary requirement imposed by Conversations. (see 'OMEMO is grayed out')
The server of all participants need to pass the OMEMO [Compliance Test](https://conversations.im/compliance/). The server of all participants need to pass the OMEMO [Compliance Test](https://conversations.im/compliance/).
In other words they either need to run Ejabberd 18.01+ or Prosody 0.11+. In other words they either need to run ejabberd 18.01+ or Prosody 0.11+.
(Alternatively it would also work if all participants had each other in their contact list; But that rarely is the case in larger group chats.) (Alternatively it would also work if all participants had each other in their contact list; But that rarely is the case in larger group chats.)
@ -371,7 +371,7 @@ Unfortunately we dont have a recommendation for iPhones right now. There are
**Note:** Starting with version 2.8.0 you will need to compile libwebrtc. **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 [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 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`. 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. Make sure to have ANDROID_HOME point to your Android SDK. Use the Android SDK Manager to install missing dependencies.

View File

@ -1,14 +1,12 @@
import com.android.build.OutputFile
// Top-level build file where you can add configuration options common to all // Top-level build file where you can add configuration options common to all
// sub-projects/modules. // sub-projects/modules.
buildscript { buildscript {
repositories { repositories {
google() google()
jcenter() mavenCentral()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:4.0.1' classpath 'com.android.tools.build:gradle:7.0.3'
} }
} }
@ -16,8 +14,8 @@ apply plugin: 'com.android.application'
repositories { repositories {
google() google()
jcenter()
mavenCentral() mavenCentral()
jcenter()
} }
configurations { configurations {
@ -26,37 +24,35 @@ configurations {
conversationsFreeCompatImplementation conversationsFreeCompatImplementation
conversationsPlaystoreCompatImplementation conversationsPlaystoreCompatImplementation
conversationsPlaystoreSystemImplementation conversationsPlaystoreSystemImplementation
quicksyPlaystoreCompatImplementation
quicksyPlaystoreSystemImplementation
quicksyFreeCompatImplementation quicksyFreeCompatImplementation
quicksyImplementation quicksyImplementation
} }
ext {
supportLibVersion = '28.0.0'
}
dependencies { dependencies {
//should remain that low because later versions introduce dependency to androidx (not sure exactly from what version) implementation 'androidx.viewpager:viewpager:1.0.0'
playstoreImplementation('com.google.firebase:firebase-messaging:17.3.4') {
playstoreImplementation('com.google.firebase:firebase-messaging:22.0.0') {
exclude group: 'com.google.firebase', module: 'firebase-core' exclude group: 'com.google.firebase', module: 'firebase-core'
exclude group: 'com.google.firebase', module: 'firebase-analytics' exclude group: 'com.google.firebase', module: 'firebase-analytics'
exclude group: 'com.google.firebase', module: 'firebase-measurement-connector' exclude group: 'com.google.firebase', module: 'firebase-measurement-connector'
} }
conversationsPlaystoreCompatImplementation("com.android.installreferrer:installreferrer:1.1.2") conversationsPlaystoreCompatImplementation("com.android.installreferrer:installreferrer:2.2")
conversationsPlaystoreSystemImplementation("com.android.installreferrer:installreferrer:1.1.2") conversationsPlaystoreSystemImplementation("com.android.installreferrer:installreferrer:2.2")
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 'org.sufficientlysecure:openpgp-api:10.0'
implementation('com.theartofdev.edmodo:android-image-cropper:2.7.+') { implementation 'com.theartofdev.edmodo:android-image-cropper:2.8.0'
exclude group: 'com.android.support', module: 'appcompat-v7' implementation 'androidx.appcompat:appcompat:1.3.1'
exclude group: 'com.android.support', module: 'exifinterface' implementation 'androidx.exifinterface:exifinterface:1.3.3'
} implementation 'androidx.cardview:cardview:1.0.0'
implementation "com.android.support:support-v13:$supportLibVersion" implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation "com.android.support:appcompat-v7:$supportLibVersion" implementation 'androidx.emoji:emoji:1.1.0'
implementation "com.android.support:exifinterface:$supportLibVersion" implementation 'com.google.android.material:material:1.4.0'
implementation "com.android.support:cardview-v7:$supportLibVersion" compatImplementation 'androidx.emoji:emoji-appcompat:1.1.0'
implementation "com.android.support:support-emoji:$supportLibVersion" conversationsFreeCompatImplementation 'androidx.emoji:emoji-bundled:1.1.0'
implementation "com.android.support:design:$supportLibVersion" quicksyFreeCompatImplementation 'androidx.emoji:emoji-bundled:1.1.0'
compatImplementation "com.android.support:support-emoji-appcompat:$supportLibVersion"
conversationsFreeCompatImplementation "com.android.support:support-emoji-bundled:$supportLibVersion"
quicksyFreeCompatImplementation "com.android.support:support-emoji-bundled:$supportLibVersion"
implementation 'org.bouncycastle:bcmail-jdk15on:1.64' implementation 'org.bouncycastle:bcmail-jdk15on:1.64'
//zxing stopped supporting Java 7 so we have to stick with 3.3.3 //zxing stopped supporting Java 7 so we have to stick with 3.3.3
//https://github.com/zxing/zxing/issues/1170 //https://github.com/zxing/zxing/issues/1170
@ -66,22 +62,23 @@ dependencies {
implementation 'org.whispersystems:signal-protocol-java:2.6.2' implementation 'org.whispersystems:signal-protocol-java:2.6.2'
implementation 'com.makeramen:roundedimageview:2.3.0' implementation 'com.makeramen:roundedimageview:2.3.0'
implementation "com.wefika:flowlayout:0.4.1" implementation "com.wefika:flowlayout:0.4.1"
implementation 'net.ypresto.androidtranscoder:android-transcoder:0.3.0' implementation 'com.otaliastudios:transcoder:0.10.4'
implementation 'org.jxmpp:jxmpp-jid:0.6.4'
implementation 'org.osmdroid:osmdroid-android:6.1.5' implementation 'org.jxmpp:jxmpp-jid:1.0.2'
implementation 'org.osmdroid:osmdroid-android:6.1.10'
implementation 'org.hsluv:hsluv:0.2' 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 'me.drakeet.support:toastcompat:1.1.0'
implementation "com.leinardi.android:speed-dial:2.0.1" implementation "com.leinardi.android:speed-dial:3.2.0"
//retrofit needs to stick with 2.6.x (https://github.com/square/retrofit/blob/master/CHANGELOG.md)
implementation "com.squareup.retrofit2:retrofit:2.6.4" implementation "com.squareup.retrofit2:retrofit:2.9.0"
implementation "com.squareup.retrofit2:converter-gson:2.6.4" implementation "com.squareup.retrofit2:converter-gson:2.9.0"
//okhttp needs to stick with 3.12.x implementation "com.squareup.okhttp3:okhttp:4.9.2"
implementation 'com.squareup.okhttp3:okhttp:3.12.12'
implementation 'com.google.guava:guava:27.1-android' implementation 'com.google.guava:guava:30.1.1-android'
quicksyImplementation 'io.michaelrocks:libphonenumber-android:8.11.1' quicksyImplementation 'io.michaelrocks:libphonenumber-android:8.12.36'
//implementation fileTree(include: ['libwebrtc-m83.aar'], dir: 'libs') // implementation fileTree(include: ['libwebrtc-m92.aar'], dir: 'libs')
implementation 'org.webrtc:google-webrtc:1.0.30039' implementation 'org.webrtc:google-webrtc:1.0.32006'
} }
ext { ext {
@ -94,28 +91,27 @@ android {
compileSdkVersion 29 compileSdkVersion 29
defaultConfig { defaultConfig {
minSdkVersion 16 minSdkVersion 21
targetSdkVersion 28 targetSdkVersion 29
versionCode 397 versionCode 4202301
versionName "2.8.10" versionName "2.10.2"
archivesBaseName += "-$versionName" archivesBaseName += "-$versionName"
applicationId "eu.sum7.conversations" applicationId "eu.sum7.conversations"
resValue "string", "applicationId", applicationId resValue "string", "applicationId", applicationId
resValue "string", "app_name", "Conv6ations" def appName = "Conv6ations"
buildConfigField "String", "LOGTAG", "\"conver6ations\"" resValue "string", "app_name", appName
buildConfigField "String", "APP_NAME", "\"$appName\"";
} }
configurations {
implementation.exclude group: 'org.jetbrains' , module:'annotations'
}
dataBinding { dataBinding {
enabled true enabled true
} }
dexOptions {
// Skip pre-dexing when running on Travis CI or when disabled via -Dpre-dex=false.
preDexLibraries = preDexEnabled && !travisBuild
jumboMode true
}
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8
@ -128,9 +124,11 @@ android {
quicksy { quicksy {
dimension "mode" dimension "mode"
applicationId = "im.quicksy.client" applicationId = "im.quicksy.client"
resValue "string", "app_name", "Quicksy"
resValue "string", "applicationId", applicationId resValue "string", "applicationId", applicationId
buildConfigField "String", "LOGTAG", "\"quicksy\""
def appName = "Quicksy"
resValue "string", "app_name", appName
buildConfigField "String", "APP_NAME", "\"$appName\"";
} }
conversations { conversations {
@ -152,14 +150,21 @@ android {
} }
sourceSets { sourceSets {
quicksyFreeSystem {
java {
srcDir 'src/quicksyFree/java'
}
}
quicksyFreeCompat { quicksyFreeCompat {
java { java {
srcDir 'src/freeCompat/java' srcDir 'src/freeCompat/java'
srcDir 'src/quicksyFree/java'
} }
} }
quicksyPlaystoreCompat { quicksyPlaystoreCompat {
java { java {
srcDir 'src/playstoreCompat/java' srcDir 'src/playstoreCompat/java'
srcDir 'src/quicksyPlaystore/java'
} }
res { res {
srcDir 'src/playstoreCompat/res' srcDir 'src/playstoreCompat/res'
@ -167,6 +172,9 @@ android {
} }
} }
quicksyPlaystoreSystem { quicksyPlaystoreSystem {
java {
srcDir 'src/quicksyPlaystore/java'
}
res { res {
srcDir 'src/quicksyPlaystore/res' srcDir 'src/quicksyPlaystore/res'
} }
@ -254,5 +262,4 @@ android {
exclude 'META-INF/BCKEY.DSA' exclude 'META-INF/BCKEY.DSA'
exclude 'META-INF/BCKEY.SF' exclude 'META-INF/BCKEY.SF'
} }
} }

View File

@ -1,7 +1,10 @@
<?xml version="1.0"?> <?xml version="1.0"?>
<?xml-stylesheet href="../style.xsl" type="text/xsl"?> <?xml-stylesheet href="../style.xsl" type="text/xsl"?>
<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"> <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
<Project xmlns="http://usefulinc.com/ns/doap#" xmlns:foaf="http://xmlns.com/foaf/0.1/" xmlns:xmpp="https://linkmauve.fr/ns/xmpp-doap#"> <Project xmlns="http://usefulinc.com/ns/doap#"
xmlns:foaf="http://xmlns.com/foaf/0.1/"
xmlns:xmpp="https://linkmauve.fr/ns/xmpp-doap#"
xmlns:schema="https://schema.org/">
<name>Conversations</name> <name>Conversations</name>
<created>2014-01-14</created> <created>2014-01-14</created>
@ -22,13 +25,21 @@
<!-- See https://github.com/ewilderj/doap/issues/49 --> <!-- See https://github.com/ewilderj/doap/issues/49 -->
<language>en</language> <language>en</language>
<logo rdf:resource="https://raw.githubusercontent.com/iNPUTmice/Conversations/master/doap.rdf"/> <schema:logo rdf:resource="https://raw.githubusercontent.com/iNPUTmice/Conversations/master/art/ic_launcher.svg"/>
<schema:screenshot rdf:resource='https://raw.githubusercontent.com/iNPUTmice/Conversations/master/fastlane/metadata/android/en-US/images/phoneScreenshots/01.png'/>
<schema:screenshot rdf:resource='https://raw.githubusercontent.com/iNPUTmice/Conversations/master/fastlane/metadata/android/en-US/images/phoneScreenshots/02.png'/>
<schema:screenshot rdf:resource='https://raw.githubusercontent.com/iNPUTmice/Conversations/master/fastlane/metadata/android/en-US/images/phoneScreenshots/03.png'/>
<schema:screenshot rdf:resource='https://raw.githubusercontent.com/iNPUTmice/Conversations/master/fastlane/metadata/android/en-US/images/phoneScreenshots/04.png'/>
<schema:screenshot rdf:resource='https://raw.githubusercontent.com/iNPUTmice/Conversations/master/fastlane/metadata/android/en-US/images/phoneScreenshots/05.png'/>
<schema:screenshot rdf:resource='https://raw.githubusercontent.com/iNPUTmice/Conversations/master/fastlane/metadata/android/en-US/images/phoneScreenshots/06.png'/>
<schema:screenshot rdf:resource='https://raw.githubusercontent.com/iNPUTmice/Conversations/master/fastlane/metadata/android/en-US/images/phoneScreenshots/07.png'/>
<schema:screenshot rdf:resource='https://raw.githubusercontent.com/iNPUTmice/Conversations/master/fastlane/metadata/android/en-US/images/phoneScreenshots/08.png'/>
<schema:screenshot rdf:resource='https://raw.githubusercontent.com/iNPUTmice/Conversations/master/fastlane/metadata/android/en-US/images/phoneScreenshots/09.png'/>
<programming-language>Java</programming-language> <programming-language>Java</programming-language>
<os>Android</os> <os>Android</os>
<!-- TODO: Categories are URIs, find a better location for them. -->
<category rdf:resource="https://linkmauve.fr/ns/xmpp-doap#category-xmpp"/> <category rdf:resource="https://linkmauve.fr/ns/xmpp-doap#category-xmpp"/>
<category rdf:resource="https://linkmauve.fr/ns/xmpp-doap#category-jabber"/> <category rdf:resource="https://linkmauve.fr/ns/xmpp-doap#category-jabber"/>
<category rdf:resource="https://linkmauve.fr/ns/xmpp-doap#category-client"/> <category rdf:resource="https://linkmauve.fr/ns/xmpp-doap#category-client"/>
@ -80,6 +91,28 @@
<xmpp:version>1.1</xmpp:version> <xmpp:version>1.1</xmpp:version>
</xmpp:SupportedXep> </xmpp:SupportedXep>
</implements> </implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0048.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.1</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0049.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.2</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0054.html"/>
<xmpp:status>partial</xmpp:status>
<xmpp:version>1.2</xmpp:version>
<xmpp:note xml:lang='en'>Avatars only</xmpp:note>
</xmpp:SupportedXep>
</implements>
<implements> <implements>
<xmpp:SupportedXep> <xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0084.html"/> <xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0084.html"/>
@ -108,6 +141,14 @@
<xmpp:version>1.5.1</xmpp:version> <xmpp:version>1.5.1</xmpp:version>
</xmpp:SupportedXep> </xmpp:SupportedXep>
</implements> </implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0153.html"/>
<xmpp:status>partial</xmpp:status>
<xmpp:version>1.1</xmpp:version>
<xmpp:note xml:lang='en'>Read only. Publication via XEP-0398</xmpp:note>
</xmpp:SupportedXep>
</implements>
<implements> <implements>
<xmpp:SupportedXep> <xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0163.html"/> <xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0163.html"/>
@ -121,7 +162,14 @@
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0166.html"/> <xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0166.html"/>
<xmpp:status>complete</xmpp:status> <xmpp:status>complete</xmpp:status>
<xmpp:version>1.1.2</xmpp:version> <xmpp:version>1.1.2</xmpp:version>
<xmpp:note>File transfer only</xmpp:note> <xmpp:note>File transfer + A/V calls</xmpp:note>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0167.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.2.1</xmpp:version>
</xmpp:SupportedXep> </xmpp:SupportedXep>
</implements> </implements>
<implements> <implements>
@ -132,6 +180,13 @@
<xmpp:note>read only</xmpp:note> <xmpp:note>read only</xmpp:note>
</xmpp:SupportedXep> </xmpp:SupportedXep>
</implements> </implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0176.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.1</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements> <implements>
<xmpp:SupportedXep> <xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0184.html"/> <xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0184.html"/>
@ -160,6 +215,27 @@
<xmpp:version>2.0.1</xmpp:version> <xmpp:version>2.0.1</xmpp:version>
</xmpp:SupportedXep> </xmpp:SupportedXep>
</implements> </implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0199.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>2.0.1</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0215.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>0.7</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0223.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.1</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements> <implements>
<xmpp:SupportedXep> <xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0234.html"/> <xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0234.html"/>
@ -211,11 +287,25 @@
</implements> </implements>
<implements> <implements>
<xmpp:SupportedXep> <xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0308.html"/> <xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0293.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.0.1</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0294.html"/>
<xmpp:status>complete</xmpp:status> <xmpp:status>complete</xmpp:status>
<xmpp:version>1.0</xmpp:version> <xmpp:version>1.0</xmpp:version>
</xmpp:SupportedXep> </xmpp:SupportedXep>
</implements> </implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0308.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.2.0</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements> <implements>
<xmpp:SupportedXep> <xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0313.html"/> <xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0313.html"/>
@ -231,6 +321,13 @@
<xmpp:note>opt-in</xmpp:note> <xmpp:note>opt-in</xmpp:note>
</xmpp:SupportedXep> </xmpp:SupportedXep>
</implements> </implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0320.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.0.0</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements> <implements>
<xmpp:SupportedXep> <xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0333.html"/> <xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0333.html"/>
@ -238,6 +335,20 @@
<xmpp:version>0.3</xmpp:version> <xmpp:version>0.3</xmpp:version>
</xmpp:SupportedXep> </xmpp:SupportedXep>
</implements> </implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0338.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.0.0</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0339.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.0.0</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements> <implements>
<xmpp:SupportedXep> <xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0352.html"/> <xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0352.html"/>
@ -245,6 +356,13 @@
<xmpp:version>0.3.0</xmpp:version> <xmpp:version>0.3.0</xmpp:version>
</xmpp:SupportedXep> </xmpp:SupportedXep>
</implements> </implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0353.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>0.3.0</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements> <implements>
<xmpp:SupportedXep> <xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0357.html"/> <xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0357.html"/>
@ -253,6 +371,13 @@
<xmpp:note>Only available in the version distributed over Google Play</xmpp:note> <xmpp:note>Only available in the version distributed over Google Play</xmpp:note>
</xmpp:SupportedXep> </xmpp:SupportedXep>
</implements> </implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0363.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>1.0.0</xmpp:version>
</xmpp:SupportedXep>
</implements>
<implements> <implements>
<xmpp:SupportedXep> <xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0368.html"/> <xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0368.html"/>
@ -328,12 +453,19 @@
<xmpp:version>0.2.0</xmpp:version> <xmpp:version>0.2.0</xmpp:version>
</xmpp:SupportedXep> </xmpp:SupportedXep>
</implements> </implements>
<implements>
<xmpp:SupportedXep>
<xmpp:xep rdf:resource="https://xmpp.org/extensions/xep-0454.html"/>
<xmpp:status>complete</xmpp:status>
<xmpp:version>0.1.0</xmpp:version>
</xmpp:SupportedXep>
</implements>
<release> <release>
<Version> <Version>
<revision>2.5.8</revision> <revision>2.9.13</revision>
<created>2019-09-12</created> <created>2021-05-03</created>
<file-release rdf:resource="https://github.com/iNPUTmice/Conversations/archive/2.5.8.tar.gz"/> <file-release rdf:resource="https://github.com/iNPUTmice/Conversations/archive/2.9.13.tar.gz"/>
</Version> </Version>
</release> </release>
</Project> </Project>

View File

@ -1,25 +0,0 @@
Conversations is a messenger for the next decade. Based on already established
internet standards that have been around for over ten years Conversations isnt
trying to replace current commercial messengers. It will simply outlive them.
Commercial, closed source products are coming and going. 15 years ago we had ICQ
which was replaced by Skype. MySpace was replaced by Facebook. WhatsApp and
Hangouts will disappear soon. Internet standards however stick around. People
are still using IRC and e-mail even though these protocols have been around for
decades. Utilizing proven standards doesnt mean one can not evolve. GMail has
revolutionized the way we look at e-mail. Firefox and Chrome have changed the
way we use the Web. Conversations will change the way we look at instant
messaging. Being less obtrusive than a telephone call instant messaging has
always played an important role in modern society. Conversations will show that
instant messaging can be fast, reliable and private. Conversations will not
force its security and privacy aspects upon the user. For those willing to use
encryption Conversations will make it as uncomplicated as possible. However
Conversations is aware that end-to-end encryption by the very principle isnt
trivial. Instead of trying the impossible and making encryption easier than
comparing a fingerprint Conversations will try to educate the willing user and
explain the necessary steps and the reasons behind them. Those unwilling to
learn about encryption will still be protected by the design principals of
Conversations. Conversations will simply not share or generate certain
information for example by encouraging the use of federated servers.
Conversations will always utilize the best available standards for encryption
and media encoding instead of reinventing the wheel. However it isnt afraid to
break with behavior patterns that have been proven ineffective.

View File

@ -1,32 +0,0 @@
* XEP-0027: Current Jabber OpenPGP Usage
* XEP-0030: Service Discovery
* XEP-0045: Multi-User Chat
* XEP-0048: Bookmarks
* XEP-0084: User Avatar
* XEP-0085: Chat State Notifications
* XEP-0092: Software Version
* XEP-0115: Entity Capabilities
* XEP-0163: Personal Eventing Protocol (avatars and nicks)
* XEP-0166: Jingle (only used for file transfer)
* XEP-0172: User Nickname
* XEP-0184: Message Delivery Receipts (reply only)
* XEP-0191: Blocking command
* XEP-0198: Stream Management
* XEP-0199: XMPP Ping
* XEP-0234: Jingle File Transfer
* XEP-0237: Roster Versioning
* XEP-0245: The /me Command
* XEP-0249: Direct MUC Invitations (receiving only)
* XEP-0260: Jingle SOCKS5 Bytestreams Transport Method
* XEP-0261: Jingle In-Band Bytestreams Transport Method
* XEP-0280: Message Carbons
* XEP-0308: Last Message Correction
* XEP-0313: Message Archive Management
* XEP-0319: Last User Interaction in Presence
* XEP-0333: Chat Markers
* XEP-0352: Client State Indication
* XEP-0357: Push Notifications
* XEP-0363: HTTP File Upload
* XEP-0368: SRV records for XMPP over TLS
* XEP-0377: Spam Reporting
* XEP-0384: OMEMO Encryption

View File

@ -1,97 +0,0 @@
Observations on implementing XMPP
=================================
After spending the last two and a half month basically writing my own XMPP
library from scratch I decided to share some of the observations I made in the
process. In part this article can be seen as a response to a blog post made by
Dr. Ing. Georg Lukas. The blog post introduces a couple of XEP (XMPP Extensions)
which make the life on mobile devices a lot easier but states that they are
currently very few implementations of those XEPs. So I went ahead and
implemented all of them in my Android XMPP client.
### General observations
The first thing I noticed is that XMPP is actually okish designed. If you were
to design a new chat protocol today you probably wouldnt choose XML again
however the protocol basically consists of only three different packages which
are quickly hidden under some sort of abstraction layer within your library.
Getting from zero to sending messages to other users actually was very simple
and straight forward. But then came the XEPs.
### Multi-User Chat
The first one was XEP-0045 Multi-User Chat. This is the one XEP of the XEPs Im
going to mention in my article which is actually wildly adopted. Most clients
and servers I know of support MUC. However the level of completeness varies.
MUC actually introduces access and permission roles which are far more complex
than what some of us are used to from IRC but a lot of clients just dont
implement them. Im not implementing them myself (at least for now) because I
somewhat doubt that someone would actually use them (however this might be some
sort of chicken or egg problem). I did find some strange bugs though which might
be interesting for other library developers. In theory a MUC server
implementation can allow a single user (same jid) to join a conference room
multiple times with the same nick from different clients. This means if someone
wants to participate in a conference from two different devices (mobile and
desktop for example) one wouldnt have to name oneself `userDesktop` and
`userMobile` but just `user`. Both ejabberd and prosody support this but with
strange side effects. Prosody for example doesnt allow a user to change its
name once two clients are “merged” by having the same nick.
### Carbons and Stream Management
Two of the other XEPs Lukas mentions — Carbons (XEP-0280) and Stream Management
(XEP-0198) — were actually fairly easy to implement. The only challenges were to
find a server to support them (I ended up running my own Prosody server) and a
desktop client to test them with. For carbons there is a patched Mcabber version
and Gajim. After implementing stream management I had very good results on my
mobile device. I had sessions running for up to 24 hours with a walking outside,
loosing mobile coverage for a few minutes and so on. The only limitation was
that I had to keep on developing and reinstalling my app.
### Off the record
And then came OTR... This is were I spend the most time debugging stuff and
trying to get things right and compatible with other clients. This is the part
were I want to help other developers not to make the same mistakes and maybe
come to some sort of consent among XMPP developers to ultimately increase the
interoperability. OTR has some down sides which make it difficult or at times
even dangerous to implement within XMPP. First of all it is a synchronous
protocol which is tunneled through a different protocol (XMPP). Synchronous
means — among other things — auto replies. (An OTR session begins with “hi Im
speaking otr give me your key” “ok cool here is my key”) And auto replies — we
know that since the first time an out of office auto responder went postal — are
dangerous. Things really start to get messy when you use one of the best
features of XMPP — multiple clients. The way XMPP works is that clients are
encouraged to send their messages to the raw jid and let the server decide what
full jid the messages are routed to. If in doubt even all of them. So what
happens when Alice sends a start-otr-message to Bobs raw jid? Bob receives the
message on his notebook as well as his cell phone. Both of them answer. Alice
gets two different replies. Shit explodes. Even if Alice sends the message to
bob/notebook chances are that Bob has carbon messages enabled and still receives
the messages on both devices. Now assuming that Bobs client is clever enough not
to auto reply to carbonated messages Bob/cellphone will still end up with a lot
of garbage messages. (Essentially the entire conversation between Alice and
Bob/notebook but unreadable of course) Therefor it should be good practice to
tag OTR messages as both private and no-copy (private is part of the carbons
XEP, no-copy is a general hint). I found that prosody for some reasons doesnt
honor the private tag on outgoing messages. While this is easily fixed I presume
that having both the private and the no-copy tag will make it more compatible
with servers or clients I dont know about yet.
#### Rules to follow when implementing OTR
To summarize my observations on implementing OTR in XMPP let me make the
following three statements.
1. While it is good practice for unencrypted messages to be send to the raw jid
and have the receiving server or user decide how they should be routed OTR
messages must be send to a specific resource. To make this work the user should
be given the option to select the presence (which can be assisted with some
educated guessing by the client based on previous messages). Furthermore a
client should encourage a user to choose meaningful presences instead of the
clients name or even random ones. Something like `/mobile`, `/notebook`,
`/desktop` is a greater assist to any one who wants to start an otr session then
`/Gajim`, `/mcabber` or `/pidgin`.
2. Messages should be tagged private and no-copy to avoid unnecessary traffic or
otr error loops with faulty clients. This tagging should be done even if your
own client doesnt support carbons.
3. When dealing with “legacy clients” — meaning clients which dont follow my
advise — a client should be extra careful not to create message loops. This
means to not respond with otr errors if a client is not 100% sure it is the only
client which received the message

3
gradle.properties Normal file
View File

@ -0,0 +1,3 @@
android.useAndroidX=true
android.enableJetifier=true
org.gradle.jvmargs=-Xmx4096m

View File

@ -1,6 +1,6 @@
#Sun Jul 26 11:32:42 CEST 2020 #Sat Nov 14 09:59:55 CET 2020
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip

View File

@ -0,0 +1 @@
• WebRTC update (with security fixes)

View File

@ -0,0 +1,4 @@
• Search individual conversations
• Notify user if message delivery fails
• Remember display names (nicks) from Quicksy users across restarts
• Add button to start Orbot (Tor) from notification if necessary

View File

@ -0,0 +1,3 @@
• Offer Easy Invite generation on supporting servers
• Display GIFs send from Movim
• store avatars in cache

View File

@ -0,0 +1,4 @@
• Fixed connectivity issues when different accounts used different SCRAM mechanisms
• Add support for SCRAM-SHA-512
• Allow P2P (Jingle) file transfer with self contact
• minor stability improvements for A/V calls

View File

@ -0,0 +1,3 @@
• Show call button for offline contacts if they previously announced support
• Back button no longer ends call when call is connected
• bug fixes

View File

@ -0,0 +1 @@
• fix crashes (error on internal database migration)

View File

@ -0,0 +1,4 @@
• Ability to select incoming call ringtone
• Fix OpenPGP key id discovery for OpenKeychain 5.6+
• Properly verify punycode TLS certificates
• Improve stability of RTP session establishment (calling)

View File

@ -0,0 +1,2 @@
• Verify A/V calls with preexisting OMEMO sessions
• Improve compatibility with non libwebrtc WebRTC implementations

View File

@ -0,0 +1 @@
• Various bug fixes around Tor support

View File

@ -0,0 +1,3 @@
• Improve call compatibility with Dino
• fix HTTP up/download for users that dont trust system CAs
• Fixed 'No Connectivity' issues on Android 7.1

View File

@ -0,0 +1,3 @@
• Always verify domain name. No user overwrite
• Support roster pre authentication
• minor A/V improvements

View File

@ -0,0 +1,3 @@
• Show black bars when remote video does not match aspect ratio of screen
• Improve search performance
• Add setting to prevent screenshots

View File

@ -0,0 +1,2 @@
• Fix issue with some videos not being compressed
• Fix rare crash when opening notification

View File

@ -0,0 +1,2 @@
• Fix crash when rendering some quotes
• Fix crash in welcome screen

View File

@ -0,0 +1 @@
• Fix usage directTLS of manuelle enter an address

9
proguard-rules.pro vendored
View File

@ -26,6 +26,15 @@
-dontwarn java.lang.** -dontwarn java.lang.**
-dontwarn javax.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.** { -keepclassmembers class eu.siacs.conversations.http.services.** {
!transient <fields>; !transient <fields>;
} }

View File

@ -1,9 +1,10 @@
package eu.siacs.conversations.ui.widget; package eu.siacs.conversations.ui.widget;
import android.content.Context; import android.content.Context;
import android.support.text.emoji.widget.EmojiAppCompatEditText;
import android.util.AttributeSet; import android.util.AttributeSet;
import androidx.emoji.widget.EmojiAppCompatEditText;
public class EmojiWrapperEditText extends EmojiAppCompatEditText { public class EmojiWrapperEditText extends EmojiAppCompatEditText {
public EmojiWrapperEditText(Context context) { public EmojiWrapperEditText(Context context) {

View File

@ -29,7 +29,7 @@
package eu.siacs.conversations.utils; package eu.siacs.conversations.utils;
import android.support.text.emoji.EmojiCompat; import androidx.emoji.text.EmojiCompat;
public class EmojiWrapper { public class EmojiWrapper {

View File

@ -20,6 +20,10 @@
android:name=".ui.MagicCreateActivity" android:name=".ui.MagicCreateActivity"
android:label="@string/create_new_account" android:label="@string/create_new_account"
android:launchMode="singleTask" /> android:launchMode="singleTask" />
<activity
android:name=".ui.EasyOnboardingInviteActivity"
android:label="@string/invite_to_app"
android:launchMode="singleTask" />
<activity <activity
android:name=".ui.ImportBackupActivity" android:name=".ui.ImportBackupActivity"
android:label="@string/restore_backup" android:label="@string/restore_backup"

View File

@ -1,7 +1,6 @@
package eu.siacs.conversations.entities; package eu.siacs.conversations.entities;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import com.google.gson.JsonSyntaxException; import com.google.gson.JsonSyntaxException;

View File

@ -12,10 +12,11 @@ import android.net.Uri;
import android.os.Binder; import android.os.Binder;
import android.os.IBinder; import android.os.IBinder;
import android.provider.OpenableColumns; import android.provider.OpenableColumns;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationManagerCompat;
import android.util.Log; import android.util.Log;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import com.google.common.base.Charsets; import com.google.common.base.Charsets;
import com.google.common.base.Stopwatch; import com.google.common.base.Stopwatch;
import com.google.common.io.CountingInputStream; import com.google.common.io.CountingInputStream;
@ -60,7 +61,7 @@ import eu.siacs.conversations.xmpp.Jid;
public class ImportBackupService extends Service { public class ImportBackupService extends Service {
private static final int NOTIFICATION_ID = 21; private static final int NOTIFICATION_ID = 21;
private static AtomicBoolean running = new AtomicBoolean(false); private static final AtomicBoolean running = new AtomicBoolean(false);
private final ImportBackupServiceBinder binder = new ImportBackupServiceBinder(); private final ImportBackupServiceBinder binder = new ImportBackupServiceBinder();
private final SerialSingleThreadExecutor executor = new SerialSingleThreadExecutor(getClass().getSimpleName()); private final SerialSingleThreadExecutor executor = new SerialSingleThreadExecutor(getClass().getSimpleName());
private final Set<OnBackupProcessed> mOnBackupProcessedListeners = Collections.newSetFromMap(new WeakHashMap<>()); private final Set<OnBackupProcessed> mOnBackupProcessedListeners = Collections.newSetFromMap(new WeakHashMap<>());

View File

@ -1,5 +1,10 @@
package eu.siacs.conversations.services; package eu.siacs.conversations.services;
import android.content.Intent;
import android.util.Log;
import eu.siacs.conversations.Config;
public class QuickConversationsService extends AbstractQuickConversationsService { public class QuickConversationsService extends AbstractQuickConversationsService {
QuickConversationsService(XmppConnectionService xmppConnectionService) { QuickConversationsService(XmppConnectionService xmppConnectionService) {
@ -25,4 +30,9 @@ public class QuickConversationsService extends AbstractQuickConversationsService
public void considerSyncBackground(boolean force) { public void considerSyncBackground(boolean force) {
} }
@Override
public void handleSmsReceived(Intent intent) {
Log.d(Config.LOGTAG,"ignoring received SMS");
}
} }

View File

@ -0,0 +1,151 @@
package eu.siacs.conversations.ui;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toast;
import androidx.databinding.DataBindingUtil;
import com.google.common.base.Strings;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
import eu.siacs.conversations.databinding.ActivityEasyInviteBinding;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.services.BarcodeProvider;
import eu.siacs.conversations.utils.EasyOnboardingInvite;
import eu.siacs.conversations.xmpp.Jid;
public class EasyOnboardingInviteActivity extends XmppActivity implements EasyOnboardingInvite.OnInviteRequested {
private ActivityEasyInviteBinding binding;
private EasyOnboardingInvite easyOnboardingInvite;
@Override
public void onCreate(final Bundle bundle) {
super.onCreate(bundle);
this.binding = DataBindingUtil.setContentView(this, R.layout.activity_easy_invite);
setSupportActionBar(binding.toolbar);
configureActionBar(getSupportActionBar(), true);
this.binding.shareButton.setOnClickListener(v -> share());
if (bundle != null && bundle.containsKey("invite")) {
this.easyOnboardingInvite = bundle.getParcelable("invite");
if (this.easyOnboardingInvite != null) {
showInvite(this.easyOnboardingInvite);
return;
}
}
this.showLoading();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.easy_onboarding_invite, menu);
final MenuItem share = menu.findItem(R.id.action_share);
share.setVisible(easyOnboardingInvite != null);
return super.onCreateOptionsMenu(menu);
}
public boolean onOptionsItemSelected(MenuItem menuItem) {
if (menuItem.getItemId() == R.id.action_share) {
share();
return true;
} else {
return super.onOptionsItemSelected(menuItem);
}
}
private void share() {
final String shareText = getString(
R.string.easy_invite_share_text,
easyOnboardingInvite.getDomain(),
easyOnboardingInvite.getShareableLink()
);
final Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, shareText);
sendIntent.setType("text/plain");
startActivity(Intent.createChooser(sendIntent, getString(R.string.share_invite_with)));
}
@Override
protected void refreshUiReal() {
invalidateOptionsMenu();
if (easyOnboardingInvite != null) {
showInvite(easyOnboardingInvite);
} else {
showLoading();
}
}
private void showLoading() {
this.binding.inProgress.setVisibility(View.VISIBLE);
this.binding.invite.setVisibility(View.GONE);
}
private void showInvite(final EasyOnboardingInvite invite) {
this.binding.inProgress.setVisibility(View.GONE);
this.binding.invite.setVisibility(View.VISIBLE);
this.binding.tapToShare.setText(getString(R.string.tap_share_button_send_invite, invite.getDomain()));
final Point size = new Point();
getWindowManager().getDefaultDisplay().getSize(size);
final int width = Math.min(size.x, size.y);
final Bitmap bitmap = BarcodeProvider.create2dBarcodeBitmap(invite.getShareableLink(), width);
binding.qrCode.setImageBitmap(bitmap);
}
@Override
public void onSaveInstanceState(Bundle bundle) {
super.onSaveInstanceState(bundle);
if (easyOnboardingInvite != null) {
bundle.putParcelable("invite", easyOnboardingInvite);
}
}
@Override
void onBackendConnected() {
if (easyOnboardingInvite != null) {
return;
}
final Intent launchIntent = getIntent();
final String accountExtra = launchIntent.getStringExtra(EXTRA_ACCOUNT);
final Jid jid = accountExtra == null ? null : Jid.ofEscaped(accountExtra);
if (jid == null) {
return;
}
final Account account = xmppConnectionService.findAccountByJid(jid);
xmppConnectionService.requestEasyOnboardingInvite(account, this);
}
public static void launch(final Account account, final Activity context) {
final Intent intent = new Intent(context, EasyOnboardingInviteActivity.class);
intent.putExtra(EXTRA_ACCOUNT, account.getJid().asBareJid().toEscapedString());
context.startActivity(intent);
}
@Override
public void inviteRequested(EasyOnboardingInvite invite) {
this.easyOnboardingInvite = invite;
Log.d(Config.LOGTAG, "invite requested");
refreshUi();
}
@Override
public void inviteRequestFailed(final String message) {
runOnUiThread(() -> {
if (!Strings.isNullOrEmpty(message)) {
Toast.makeText(this, message, Toast.LENGTH_LONG).show();
}
finish();
});
}
}

View File

@ -5,21 +5,21 @@ import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.ServiceConnection; import android.content.ServiceConnection;
import android.databinding.DataBindingUtil;
import android.net.Uri; import android.net.Uri;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.IBinder; import android.os.IBinder;
import android.support.design.widget.Snackbar;
import android.support.v4.content.ContextCompat;
import android.support.v7.app.AlertDialog;
import android.support.v7.widget.Toolbar;
import android.util.Log; import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.databinding.DataBindingUtil;
import com.google.android.material.snackbar.Snackbar;
import java.io.IOException; import java.io.IOException;
import java.util.List; import java.util.List;
@ -29,6 +29,7 @@ import eu.siacs.conversations.databinding.ActivityImportBackupBinding;
import eu.siacs.conversations.databinding.DialogEnterPasswordBinding; import eu.siacs.conversations.databinding.DialogEnterPasswordBinding;
import eu.siacs.conversations.services.ImportBackupService; import eu.siacs.conversations.services.ImportBackupService;
import eu.siacs.conversations.ui.adapter.BackupFileAdapter; import eu.siacs.conversations.ui.adapter.BackupFileAdapter;
import eu.siacs.conversations.ui.util.SettingsUtils;
import eu.siacs.conversations.utils.ThemeHelper; import eu.siacs.conversations.utils.ThemeHelper;
public class ImportBackupActivity extends ActionBarActivity implements ServiceConnection, ImportBackupService.OnBackupFilesLoaded, BackupFileAdapter.OnItemClickedListener, ImportBackupService.OnBackupProcessed { public class ImportBackupActivity extends ActionBarActivity implements ServiceConnection, ImportBackupService.OnBackupFilesLoaded, BackupFileAdapter.OnItemClickedListener, ImportBackupService.OnBackupProcessed {
@ -48,13 +49,19 @@ public class ImportBackupActivity extends ActionBarActivity implements ServiceCo
setTheme(this.mTheme); setTheme(this.mTheme);
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
binding = DataBindingUtil.setContentView(this, R.layout.activity_import_backup); binding = DataBindingUtil.setContentView(this, R.layout.activity_import_backup);
setSupportActionBar((Toolbar) binding.toolbar); setSupportActionBar(binding.toolbar);
setLoadingState(savedInstanceState != null && savedInstanceState.getBoolean("loading_state", false)); setLoadingState(savedInstanceState != null && savedInstanceState.getBoolean("loading_state", false));
this.backupFileAdapter = new BackupFileAdapter(); this.backupFileAdapter = new BackupFileAdapter();
this.binding.list.setAdapter(this.backupFileAdapter); this.binding.list.setAdapter(this.backupFileAdapter);
this.backupFileAdapter.setOnItemClickedListener(this); this.backupFileAdapter.setOnItemClickedListener(this);
} }
@Override
protected void onResume(){
super.onResume();
SettingsUtils.applyScreenshotPreventionSetting(this);
}
@Override @Override
public boolean onCreateOptionsMenu(final Menu menu) { public boolean onCreateOptionsMenu(final Menu menu) {
getMenuInflater().inflate(R.menu.import_backup, menu); getMenuInflater().inflate(R.menu.import_backup, menu);
@ -124,7 +131,8 @@ public class ImportBackupActivity extends ActionBarActivity implements ServiceCo
try { try {
final ImportBackupService.BackupFile backupFile = ImportBackupService.BackupFile.read(this, uri); final ImportBackupService.BackupFile backupFile = ImportBackupService.BackupFile.read(this, uri);
showEnterPasswordDialog(backupFile, finishOnCancel); 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(); Snackbar.make(binding.coordinator, R.string.not_a_backup_file, Snackbar.LENGTH_LONG).show();
} }
} }
@ -180,6 +188,7 @@ public class ImportBackupActivity extends ActionBarActivity implements ServiceCo
@Override @Override
public void onActivityResult(int requestCode, int resultCode, Intent intent) { public void onActivityResult(int requestCode, int resultCode, Intent intent) {
super.onActivityResult(requestCode, resultCode, intent);
if (resultCode == RESULT_OK) { if (resultCode == RESULT_OK) {
if (requestCode == 0xbac) { if (requestCode == 0xbac) {
openBackupFileFromUri(intent.getData(), false); openBackupFileFromUri(intent.getData(), false);
@ -224,15 +233,17 @@ public class ImportBackupActivity extends ActionBarActivity implements ServiceCo
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.action_open_backup_file) { if (item.getItemId() == R.id.action_open_backup_file) {
Intent intent = new Intent(Intent.ACTION_GET_CONTENT); openBackupFile();
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);
return true; return true;
} }
return super.onOptionsItemSelected(item); 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);
}
} }

View File

@ -2,14 +2,14 @@ package eu.siacs.conversations.ui;
import android.content.Intent; import android.content.Intent;
import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo;
import android.databinding.DataBindingUtil;
import android.os.Bundle; import android.os.Bundle;
import android.support.v7.widget.Toolbar;
import android.text.Editable; import android.text.Editable;
import android.text.TextWatcher; import android.text.TextWatcher;
import android.view.View; import android.view.View;
import android.widget.Toast; import android.widget.Toast;
import androidx.databinding.DataBindingUtil;
import java.security.SecureRandom; import java.security.SecureRandom;
import eu.siacs.conversations.Config; import eu.siacs.conversations.Config;
@ -61,7 +61,7 @@ public class MagicCreateActivity extends XmppActivity implements TextWatcher {
} }
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
this.binding = DataBindingUtil.setContentView(this, R.layout.magic_create); this.binding = DataBindingUtil.setContentView(this, R.layout.magic_create);
setSupportActionBar((Toolbar) this.binding.toolbar); setSupportActionBar(this.binding.toolbar);
configureActionBar(getSupportActionBar(), this.domain == null); configureActionBar(getSupportActionBar(), this.domain == null);
if (username != null && domain != null) { if (username != null && domain != null) {
binding.title.setText(R.string.your_server_invitation); binding.title.setText(R.string.your_server_invitation);

View File

@ -5,9 +5,6 @@ import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.security.KeyChain; import android.security.KeyChain;
import android.security.KeyChainAliasCallback; import android.security.KeyChainAliasCallback;
import android.support.annotation.NonNull;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AlertDialog;
import android.util.Pair; import android.util.Pair;
import android.view.ContextMenu; import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo; import android.view.ContextMenu.ContextMenuInfo;
@ -18,6 +15,10 @@ import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.ListView; import android.widget.ListView;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog;
import org.openintents.openpgp.util.OpenPgpApi; import org.openintents.openpgp.util.OpenPgpApi;
import java.util.ArrayList; import java.util.ArrayList;
@ -31,8 +32,8 @@ import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate; import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate;
import eu.siacs.conversations.ui.adapter.AccountAdapter; import eu.siacs.conversations.ui.adapter.AccountAdapter;
import eu.siacs.conversations.ui.util.MenuDoubleTabUtil; import eu.siacs.conversations.ui.util.MenuDoubleTabUtil;
import eu.siacs.conversations.xmpp.XmppConnection;
import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.Jid;
import eu.siacs.conversations.xmpp.XmppConnection;
import static eu.siacs.conversations.utils.PermissionUtils.allGranted; import static eu.siacs.conversations.utils.PermissionUtils.allGranted;
import static eu.siacs.conversations.utils.PermissionUtils.writeGranted; import static eu.siacs.conversations.utils.PermissionUtils.writeGranted;
@ -226,7 +227,8 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda
@Override @Override
public void onRequestPermissionsResult(int requestCode, @NonNull String permissions[], @NonNull int[] grantResults) { public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (grantResults.length > 0) { if (grantResults.length > 0) {
if (allGranted(grantResults)) { if (allGranted(grantResults)) {
switch (requestCode) { switch (requestCode) {

View File

@ -2,12 +2,12 @@ package eu.siacs.conversations.ui;
import android.content.Intent; import android.content.Intent;
import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo;
import android.databinding.DataBindingUtil;
import android.os.Bundle; import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.MenuItem; import android.view.MenuItem;
import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;
import java.util.List; import java.util.List;
import eu.siacs.conversations.R; import eu.siacs.conversations.R;
@ -66,7 +66,7 @@ public class PickServerActivity extends XmppActivity {
} }
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
ActivityPickServerBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_pick_server); ActivityPickServerBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_pick_server);
setSupportActionBar((Toolbar) binding.toolbar); setSupportActionBar(binding.toolbar);
configureActionBar(getSupportActionBar()); configureActionBar(getSupportActionBar());
binding.useCim.setOnClickListener(v -> { binding.useCim.setOnClickListener(v -> {
final Intent intent = new Intent(this, MagicCreateActivity.class); final Intent intent = new Intent(this, MagicCreateActivity.class);

View File

@ -4,20 +4,19 @@ import android.Manifest;
import android.content.ActivityNotFoundException; import android.content.ActivityNotFoundException;
import android.content.Intent; import android.content.Intent;
import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
import android.databinding.DataBindingUtil;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.security.KeyChain; import android.security.KeyChain;
import android.security.KeyChainAliasCallback; import android.security.KeyChainAliasCallback;
import android.support.annotation.NonNull;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log; import android.util.Log;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
@ -107,7 +106,8 @@ public class WelcomeActivity extends XmppActivity implements XmppConnectionServi
} }
@Override @Override
public void onNewIntent(Intent intent) { public void onNewIntent(final Intent intent) {
super.onNewIntent(intent);
if (intent != null) { if (intent != null) {
setIntent(intent); setIntent(intent);
} }
@ -120,7 +120,7 @@ public class WelcomeActivity extends XmppActivity implements XmppConnectionServi
} }
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
ActivityWelcomeBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_welcome); ActivityWelcomeBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_welcome);
setSupportActionBar((Toolbar) binding.toolbar); setSupportActionBar(binding.toolbar);
configureActionBar(getSupportActionBar(), false); configureActionBar(getSupportActionBar(), false);
binding.registerNewAccount.setOnClickListener(v -> { binding.registerNewAccount.setOnClickListener(v -> {
final Intent intent = new Intent(this, PickServerActivity.class); final Intent intent = new Intent(this, PickServerActivity.class);
@ -202,6 +202,7 @@ public class WelcomeActivity extends XmppActivity implements XmppConnectionServi
@Override @Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
UriHandlerActivity.onRequestPermissionResult(this, requestCode, grantResults); UriHandlerActivity.onRequestPermissionResult(this, requestCode, grantResults);
if (grantResults.length > 0) { if (grantResults.length > 0) {
if (allGranted(grantResults)) { if (allGranted(grantResults)) {

View File

@ -1,13 +1,10 @@
package eu.siacs.conversations.ui.adapter; package eu.siacs.conversations.ui.adapter;
import android.content.res.Resources; import android.content.res.Resources;
import android.databinding.DataBindingUtil;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.text.format.DateUtils; import android.text.format.DateUtils;
import android.util.DisplayMetrics; import android.util.DisplayMetrics;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@ -15,6 +12,10 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.ImageView; import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.databinding.DataBindingUtil;
import androidx.recyclerview.widget.RecyclerView;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;

View File

@ -0,0 +1,11 @@
package eu.siacs.conversations.utils;
import android.content.Context;
import eu.siacs.conversations.xmpp.Jid;
public class PhoneNumberUtilWrapper {
public static String toFormattedPhoneNumber(Context context, Jid jid) {
throw new AssertionError("This method is not implemented in Conversations");
}
}

View File

@ -0,0 +1,83 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="?attr/color_background_primary"
android:orientation="vertical">
<include
android:id="@+id/toolbar"
layout="@layout/toolbar" />
<LinearLayout
android:id="@+id/in_progress"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:visibility="gone">
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center" />
</LinearLayout>
<RelativeLayout
android:id="@+id/invite"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:layout_marginTop="@dimen/activity_vertical_margin"
android:layout_marginRight="@dimen/activity_horizontal_margin"
android:layout_marginBottom="@dimen/activity_vertical_margin"
android:visibility="visible">
<TextView
android:id="@+id/tap_to_share"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/tap_share_button_send_invite"
android:textAppearance="@style/TextAppearance.Conversations.Body1" />
<TextView
android:id="@+id/scan_the_code"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/tap_to_share"
android:layout_marginTop="24sp"
android:text="@string/if_contact_is_nearby_use_qr"
android:textAppearance="@style/TextAppearance.Conversations.Body1" />
<ImageView
android:id="@+id/qr_code"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_above="@+id/share_button"
android:layout_below="@id/scan_the_code"
android:layout_alignParentStart="true"
android:layout_alignParentRight="true"
android:layout_centerHorizontal="true"
android:layout_margin="24sp"
android:scaleType="fitCenter" />
<Button
android:id="@+id/share_button"
style="@style/Widget.Conversations.Button.Borderless"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:minWidth="0dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:text="@string/share"
android:layout_centerHorizontal="true"
android:textColor="?attr/colorAccent" />
</RelativeLayout>
</LinearLayout>
</layout>

View File

@ -26,20 +26,20 @@
<android.support.design.widget.CoordinatorLayout <androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="@+id/coordinator" android:id="@+id/coordinator"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="?attr/color_background_primary"> android:background="?attr/color_background_primary">
<android.support.v7.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/list" android:id="@+id/list"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="?attr/color_background_primary" android:background="?attr/color_background_primary"
android:orientation="vertical" android:orientation="vertical"
app:layoutManager="android.support.v7.widget.LinearLayoutManager" /> app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
</android.support.design.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>
</LinearLayout> </LinearLayout>
</layout> </layout>

View File

@ -22,7 +22,7 @@
android:text="@string/restore_warning" android:text="@string/restore_warning"
android:textAppearance="@style/TextAppearance.Conversations.Body1"/> android:textAppearance="@style/TextAppearance.Conversations.Body1"/>
<android.support.design.widget.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:id="@+id/account_password_layout" android:id="@+id/account_password_layout"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
@ -42,6 +42,6 @@
android:textColor="?attr/edit_text_color" android:textColor="?attr/edit_text_color"
style="@style/Widget.Conversations.EditText"/> style="@style/Widget.Conversations.EditText"/>
</android.support.design.widget.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
</LinearLayout> </LinearLayout>
</layout> </layout>

View File

@ -0,0 +1,10 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:id="@+id/action_share"
android:icon="?attr/icon_share"
android:title="@string/invite"
app:showAsAction="always" />
</menu>

View File

@ -3,4 +3,18 @@
<string name="pick_a_server">اختر مزود خدمة XMPP الخاص بك</string> <string name="pick_a_server">اختر مزود خدمة XMPP الخاص بك</string>
<string name="use_chat.sum7.eu">استخدِم chat.sum7.eu</string> <string name="use_chat.sum7.eu">استخدِم chat.sum7.eu</string>
<string name="create_new_account">أنشئ حسابًا جديدًا</string> <string name="create_new_account">أنشئ حسابًا جديدًا</string>
<string name="do_you_have_an_account">هل تملك حساب XMPP؟؟ قد يكون ذلك ممكنا لو كنت تستعمل خدمة XMPP أخرى أو إستعملت تطبيق كونفرسايشنز سابقا. أو يمكنك صنع حساب XMPP جديد الآن.
ملاحظة: بعض خدمات البريد الإلكتروني تقدم حسابات XMPP.</string>
<string name="server_select_text">XMPP هي خدمة مستقلة للتواصل بشبكة الرسائل المباشرة. يمكنك إستعمال هذه الخدمة مع أي خادم XMPP تختاره.
سعيا لراحتك جعلنا خلق حساب في كونفيرسايشنز سهلا مع مقدم خدمة خاص بالإستعمال مع كونفيرسايشنز.</string>
<string name="magic_create_text_on_x">لقد تمت دعوتك لـ %1$s. سيتم دلّك على طريقة صنع حساب.
عندما تختار %1$sكمقدّم خدمة سيصبح من الممكن لك التواصل مع مستعملين من أي خادم آخر عن طريق إعطائهم عنوانك الكامل على XMPP.</string>
<string name="magic_create_text_fixed">تمّت دعوتك إلى %1$s. تم إختيار إسم مستخدم خاص بك. سيتم قيادتك عبر طريقة صنع حساب.
سيمكنك التواصل مع مستخدمين من مزودين آخرين عبر إعطائهم كامل عنوانك XMPP.</string>
<string name="your_server_invitation">سيرفر دعوتك</string>
<string name="improperly_formatted_provisioning">لم يتم التقاط الكود بطريقة جيّدة</string>
<string name="tap_share_button_send_invite">إضغط على زر مشاركة لترسل إلى المتصل بك دعوة إلى %1$s.</string>
<string name="if_contact_is_nearby_use_qr">إذا كان المتصل بك قريبا منك، يمكنه فحص الكود بالأسفل ليقبل دعوتك.</string>
<string name="easy_invite_share_text">إنظم %1$s وتحدّث معي: %2$s</string>
<string name="share_invite_with">شارك إستدعاء مع...</string>
</resources> </resources>

View File

@ -5,4 +5,13 @@
<string name="create_new_account">Създаване не нов профил</string> <string name="create_new_account">Създаване не нов профил</string>
<string name="do_you_have_an_account">Имате ли вече XMPP профил? Това може да се случи, ако вече използвате друг клиент на XMPP или сте използвали преди това Conversations. Ако не, можете да създадете нов XMPP профил в момента.\nСъвет: Някои доставчици на имейл също предоставят XMPP профили. <string name="do_you_have_an_account">Имате ли вече XMPP профил? Това може да се случи, ако вече използвате друг клиент на XMPP или сте използвали преди това Conversations. Ако не, можете да създадете нов XMPP профил в момента.\nСъвет: Някои доставчици на имейл също предоставят XMPP профили.
 </string>  </string>
<string name="server_select_text">XMPP е мрежа за общуване чрез мигновени съобщения, която не е обвързана с конкретен доставчик. Можете да използвате клиента с всеки сървър, който работи с XMPP.\nЗа Ваше удобство, ние предоставяме лесен начин да си създадете профил в chat.sum7.eu — сървър, пригоден да работи добре с Conversations.</string>
<string name="magic_create_text_on_x">Бяхте поканен(а) в %1$s. Ще Ви преведем през процеса на създаване на акаунт.\nИзбирайки %1$s за доставчик, Вие ще можете да общувате и с потребители на други доставчици, като им предоставите своя пълен адрес за XMPP.</string>
<string name="magic_create_text_fixed">Бяхте поканен(а) в %1$s. Вече Ви избрахме потребителско име. Ще Ви преведем през процеса на създаване на акаунт.\nЩе можете да общувате и с потребители на други доставчици, като им предоставите своя пълен адрес за XMPP.</string>
<string name="your_server_invitation">Вашата покана за сървъра</string>
<string name="improperly_formatted_provisioning">Неправилно форматиран код за достъп</string>
<string name="tap_share_button_send_invite">Докоснете бутона за споделяне, за да изпратите на контакта си покана за %1$s.</string>
<string name="if_contact_is_nearby_use_qr">Ако контактът Ви е наблизо, може да сканира кода по-долу, за да приеме поканата Ви.</string>
<string name="easy_invite_share_text">Присъедини се в %1$s и си пиши с мен: %2$s</string>
<string name="share_invite_with">Споделяне на поканата чрез…</string>
</resources> </resources>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pick_a_server">XMPP সার্ভার নির্বাচন করুন</string>
<string name="use_chat.sum7.eu">chat.sum7.eu ব্যবহার করা যাক</string>
<string name="create_new_account">নতুন অ্যকাউন্ট তৈরী করা যাক</string>
<string name="do_you_have_an_account">আপনার কি একটা XMPP অ্যকাউন্ট ইতিমধ্যে করা আছে? সেরকমটা হতেই পারে যদি এর আগে আপনি কোনো অন্য XMPP প্রোগ্রাম বা অ্যাপ ব্যবহার করে থাকেন। এই মুহুর্তে আরেকটা অ্যকাউন্ট তৈরী করা সম্ভব না।‌\nHint: মাঝে মাঝে ইমেল অ্যকাউন্ট খুললেও এরকম অ্যকাউন্ট নিজে থেকেই তৈরী হয়ে যায়।</string>
<string name="server_select_text">XMPP কোনো একটি নির্দিষ্ট সংস্থার উপরে নির্ভরশীল নয়। এই অ্যপটি আপনি যেকোনো সংস্থার XMPP সার্ভারের সাথে ব্যবহার করতে পারেন।\nমনে রাখবেন, সুধুমাত্র আপনার সুবিধার্থেই chat.sum7.eu -এ আপনার জন্যে একটি অ্যকাউন্ট তৈরী করে দেওয়া হয়েছে। Conversations অ্যপটি এই সার্ভারের সাথে সবথেকে বেশী কার্যকারী।</string>
<string name="magic_create_text_on_x">আপনাকে %1$s-এ আমন্ত্রিত করা হয়েছে। অ্যকাউন্ট তৈরী করার সময় আপনাকে সাহায্য করা হবে।\n%1$s ব্যবহার করলেও, অন্য সেবা-প্রদানকারী সংস্থার ব্যবহারকারীদের সাথে আপনি কথা বলতে পারবেন, আপনার সম্পূর্ণ XMPP অ্যড্রেস তাদেরকে বলে দিয়ে।</string>
<string name="magic_create_text_fixed">আপনাকে %1$s-এ নিমন্ত্রণ করা হয়েছে। একটি username-ও আপনার জন্যে নির্দিষ্ট করে রাখা হয়েছে। অ্যকাউন্ট তৈরী করার সময় আপনাকে সাহায্য করা হবে।\nঅন্য XMPP সেবা প্রদানকারী সংস্থার ব্যবহারকারীদের সাথে আপনিও কথা বলতে পারবেন, আপনার সম্পূর্ণ XMPP অ্যড্রেস তাদেরকে বলে দিয়ে।</string>
<string name="your_server_invitation">আপনার নিমন্ত্রণপত্র, সার্ভার থেকে</string>
<string name="improperly_formatted_provisioning">Provisioning code-এ গরমিল আছে</string>
<string name="tap_share_button_send_invite">Share বোতামটা টিপে %1$s-কে একটি আমন্ত্রপত্র পাঠান</string>
<string name="if_contact_is_nearby_use_qr">পরিচিত ব্যক্তি যদি নিকটেই থাকেন, তাহলে তারা এই কোডটাও স্ক্যান করে নিতে পারেন</string>
<string name="easy_invite_share_text">%1$sতে এসো, আর আমার সাথে কথা বলো: %2$s</string>
<string name="share_invite_with">একটি আমন্ত্রণপত্র দেওয়া যাক...</string>
</resources>

View File

@ -2,4 +2,16 @@
<resources> <resources>
<string name="pick_a_server">Triï el seu proveïdor de XMPP <string name="pick_a_server">Triï el seu proveïdor de XMPP
</string> </string>
<string name="use_chat.sum7.eu">Fer servir chat.sum7.eu</string>
<string name="create_new_account">Crear un compte nou</string>
<string name="do_you_have_an_account">Ja tens un compte XMPP? Aquest podria ser el cas si ja estàs usant un client XMPP diferent o has usat Converses abans. Si no, pots crear un nou compte XMPP ara mateix.\nPista: Alguns proveïdors de correu electrònic també proporcionen comptes XMPP.</string>
<string name="server_select_text">XMPP és una xarxa de missatgeria instantània independent del proveïdor. Pots usar aquest client amb qualsevol servidor XMPP que triïs. No obstant això, per a la teva conveniència, hem fet fàcil la creació d\'un compte en Conversaciones.im¹; un proveïdor especialment adequat per a l\'ús amb Conversations.</string>
<string name="magic_create_text_on_x">Has estat convidat a %1$s. Et guiarem a través del procés de creació d\'un compte.\nEn triar%1$s com a proveïdor podràs comunicar-se amb els usuaris d\'altres proveïdors donant-los la seva adreça XMPP completa.</string>
<string name="magic_create_text_fixed">Has estat convidat a %1$s . Ja s\'ha triat un nom d\'usuari per a tu. Et guiarem en el procés de creació d\'un compte. Podràs comunicar-te amb usuaris d\'altres proveïdors donant-los la teva adreça XMPP completa.</string>
<string name="your_server_invitation">La teva invitació al servidor</string>
<string name="improperly_formatted_provisioning">Codi d\'aprovisionament mal formatat</string>
<string name="tap_share_button_send_invite">Toca el botó de compartir per a enviar al teu contacte una invitació a %1$s .</string>
<string name="if_contact_is_nearby_use_qr">Si el teu contacte està a prop, també pot escanejar el codi de baix per a acceptar la teva invitació.</string>
<string name="easy_invite_share_text">Uneix-te %1$s i xerra amb mi: %2$s</string>
<string name="share_invite_with">Comparteix la invitació amb...</string>
</resources> </resources>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pick_a_server">Vælg din XMPP-udbyder</string>
<string name="use_chat.sum7.eu">Brug chat.sum7.eu</string>
<string name="create_new_account">Opret ny konto</string>
<string name="do_you_have_an_account">Har du allerede en XMPP-konto? Dette kan være tilfældet, hvis du allerede bruger en anden XMPP-klient eller har brugt Conversations før. Hvis ikke, kan du lige nu oprette en ny XMPP-konto.\nTip: Nogle e-mail-udbydere leverer også XMPP-konti.</string>
<string name="server_select_text">XMPP er et udbyderuafhængigt onlinemeddelelsesnetværk. Du kan bruge denne klient med hvilken XMPP-server du end vælger.\nMen for din nemhedsskyld har vi gjort vi det let at oprette en konto på chat.sum7.eu; en udbyder, der er specielt velegnet til brug med Conversations.</string>
<string name="magic_create_text_on_x">Du er blevet inviteret til %1$s. Vi guider dig gennem processen med at oprette en konto.\nNår du vælger %1$s som udbyder, kan du kommunikere med brugere fra andre udbydere ved at give dem din fulde XMPP-adresse.</string>
<string name="magic_create_text_fixed">Du er blevet inviteret til %1$s. Der er allerede valgt et brugernavn til dig. Vi guider dig gennem processen med at oprette en konto.\nDu vil være i stand til at kommunikere med brugere fra andre udbydere ved at give dem din fulde XMPP-adresse.</string>
<string name="your_server_invitation">Din server invitation</string>
<string name="improperly_formatted_provisioning">Forkert formateret klargøringskode</string>
<string name="tap_share_button_send_invite">Tryk på deleknappen for at sende din kontakt en invitation til %1$s.</string>
<string name="if_contact_is_nearby_use_qr">Hvis din kontakt er i nærheden, kan de også skanne koden nedenfor for at acceptere din invitation.</string>
<string name="easy_invite_share_text">Deltag med %1$s og chat med mig: %2$s</string>
<string name="share_invite_with">Del invitation med...</string>
</resources>

View File

@ -9,4 +9,8 @@
<string name="magic_create_text_fixed">Du wurdest zu %1$seingeladen. Ein Benutzername ist bereits für dich ausgewählt worden. Wir führen dich durch den Prozess der Kontoerstellung.\nDu kannst mit Nutzern anderer Anbieter kommunizieren, indem du ihnen deine vollständige XMPP-Adresse gibst.</string> <string name="magic_create_text_fixed">Du wurdest zu %1$seingeladen. Ein Benutzername ist bereits für dich ausgewählt worden. Wir führen dich durch den Prozess der Kontoerstellung.\nDu kannst mit Nutzern anderer Anbieter kommunizieren, indem du ihnen deine vollständige XMPP-Adresse gibst.</string>
<string name="your_server_invitation">Deine Einladung für den Server</string> <string name="your_server_invitation">Deine Einladung für den Server</string>
<string name="improperly_formatted_provisioning">Falsch formatierter Provisionierungscode</string> <string name="improperly_formatted_provisioning">Falsch formatierter Provisionierungscode</string>
<string name="tap_share_button_send_invite">Tippe auf die \"Teilen\"-Schaltfläche, um deinem Kontakt eine Einladung an %1$s zu senden.</string>
<string name="if_contact_is_nearby_use_qr">Wenn dein Kontakt in der Nähe ist, kann er auch den untenstehenden Code einscannen, um deine Einladung anzunehmen.</string>
<string name="easy_invite_share_text">Komme zu %1$s und chatte mit mir: %2$s</string>
<string name="share_invite_with">Einladung teilen mit…</string>
</resources> </resources>

View File

@ -5,7 +5,12 @@
<string name="create_new_account">Δημιουργία νέου λογαριασμού</string> <string name="create_new_account">Δημιουργία νέου λογαριασμού</string>
<string name="do_you_have_an_account">Έχετε ήδη λογαριασμό XMPP; Αυτό μπορεί να συμβαίνει αν ήδη χρησιμοποιείτε ένα άλλο πρόγραμμα XMPP ή έχετε χρησιμοποιήσει το Conversations παλιότερα. Αν όχι, μπορείτε να δημιουργήσετε ένα νέο λογαριασμό XMPP τώρα.\nΧρήσιμη πληροφορία: Κάποιοι πάροχοι e-mail παρέχουν επίσης και λογαριασμούς XMPP.</string> <string name="do_you_have_an_account">Έχετε ήδη λογαριασμό XMPP; Αυτό μπορεί να συμβαίνει αν ήδη χρησιμοποιείτε ένα άλλο πρόγραμμα XMPP ή έχετε χρησιμοποιήσει το Conversations παλιότερα. Αν όχι, μπορείτε να δημιουργήσετε ένα νέο λογαριασμό XMPP τώρα.\nΧρήσιμη πληροφορία: Κάποιοι πάροχοι e-mail παρέχουν επίσης και λογαριασμούς XMPP.</string>
<string name="server_select_text">Το XMPP είναι ένα δίκτυο άμεσης ανταλλαγής μηνυμάτων ανεξάρτητο παρόχου. Μπορείτε να χρησιμοποιήσετε αυτό το πρόγραμμα με όποιον διακομιστή XMPP επιθυμείτε.\nΓια διευκόλυνση πάντως μπορείτε να δημιουργήσετε έναν λογαριασμό στο chat.sum7.eu, έναν πάροχο ειδικά σχεδιασμένο για χρήση με το Conversations.</string> <string name="server_select_text">Το XMPP είναι ένα δίκτυο άμεσης ανταλλαγής μηνυμάτων ανεξάρτητο παρόχου. Μπορείτε να χρησιμοποιήσετε αυτό το πρόγραμμα με όποιον διακομιστή XMPP επιθυμείτε.\nΓια διευκόλυνση πάντως μπορείτε να δημιουργήσετε έναν λογαριασμό στο chat.sum7.eu, έναν πάροχο ειδικά σχεδιασμένο για χρήση με το Conversations.</string>
<string name="magic_create_text_on_x">Έχετε προσκληθεί στο %1$s. Ένα όνομα χρήστη έχει ήδη επιλεγεί για εσάς. Θα σας καθοδηγήσουμε στη διαδικασία δημιουργίας λογαριασμού.\nΕπιλέγοντας τον %1$s ως πάροχο θα μπορείτε να επικοινωνείτε με χρήστες άλλων παρόχων δίνοντάς τους την πλήρη διεύθυνση XMPP σας.</string> <string name="magic_create_text_on_x">Έχετε προσκληθεί στο %1$s. Θα σας καθοδηγήσουμε στη διαδικασία δημιουργίας λογαριασμού.\nΕπιλέγοντας τον %1$s ως πάροχο θα μπορείτε να επικοινωνείτε με χρήστες άλλων παρόχων δίνοντάς τους την πλήρη διεύθυνση XMPP σας.</string>
<string name="magic_create_text_fixed">Έχετε προσκληθεί στο %1$s. Ένα όνομα χρήστη έχει ήδη επιλεγεί για εσάς. Θα σας καθοδηγήσουμε στη διαδικασία δημιουργίας λογαριασμού.\nΘα μπορείτε να επικοινωνείτε με χρήστες άλλων παρόχων δίνοντάς τους την πλήρη διεύθυνση XMPP σας.</string> <string name="magic_create_text_fixed">Έχετε προσκληθεί στο %1$s. Ένα όνομα χρήστη έχει ήδη επιλεγεί για εσάς. Θα σας καθοδηγήσουμε στη διαδικασία δημιουργίας λογαριασμού.\nΘα μπορείτε να επικοινωνείτε με χρήστες άλλων παρόχων δίνοντάς τους την πλήρη διεύθυνση XMPP σας.</string>
<string name="your_server_invitation">Η πρόσκλησή σας στον διακομιστή</string> <string name="your_server_invitation">Η πρόσκλησή σας στον διακομιστή</string>
<string name="improperly_formatted_provisioning">Λάθος μορφοποίηση κώδικα παροχής</string>
<string name="tap_share_button_send_invite">Πατήστε το πλήκτρο διαμοιρασμού για να στείλετε στην επαφή σας μια πρόσκληση στο %1$s.</string>
<string name="if_contact_is_nearby_use_qr">Αν η επαφή σας βρίσκεται κοντά σας, μπορεί επίσης να σαρώσει τον κωδικό παρακάτω για να αποδεχτεί την πρόσκλησή σας.</string>
<string name="easy_invite_share_text">Μπείτε στο %1$s και συνομιλήστε μαζί μου: %2$s</string>
<string name="share_invite_with">Διαμοιρασμός πρόσκλησης με...</string>
</resources> </resources>

View File

@ -9,4 +9,8 @@
<string name="magic_create_text_fixed">Has sido invitado a %1$s. Un nombre de usuario ya ha sido escogido para ti. Te guiaremos durante el proceso de creación de la cuenta.\nPodrás comunicarte con otros usuarios de otros servidores proporcionándoles tu dirección XMPP completa. </string> <string name="magic_create_text_fixed">Has sido invitado a %1$s. Un nombre de usuario ya ha sido escogido para ti. Te guiaremos durante el proceso de creación de la cuenta.\nPodrás comunicarte con otros usuarios de otros servidores proporcionándoles tu dirección XMPP completa. </string>
<string name="your_server_invitation">Tu invitación al servidor</string> <string name="your_server_invitation">Tu invitación al servidor</string>
<string name="improperly_formatted_provisioning">Código de abastecimiento formateado incorrectamente</string> <string name="improperly_formatted_provisioning">Código de abastecimiento formateado incorrectamente</string>
<string name="tap_share_button_send_invite">Pulsa el botón de compartir para enviar a tu contacto una invitación a %1$s.</string>
<string name="if_contact_is_nearby_use_qr">Si tu contacto está cerca, también puede escanear el código mostrado debajo para aceptar tu invitación.</string>
<string name="easy_invite_share_text">Únete a %1$s y chatea conmigo: %2$s</string>
<string name="share_invite_with">Compartir invitación con...</string>
</resources> </resources>

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="pick_a_server">لطفا سرویس دهنده پیام خود را انتخاب نمائید. برای مثال artalk.im</string> <string name="pick_a_server">لطفا سرویس دهنده پیام خود را انتخاب نمائید. برای مثال artalk.im</string>
<string name="use_conversations.im">از Conversations.im استفاده کنید</string> <string name="use_chat.sum7.eu">از Conversations.im استفاده کنید</string>
<string name="create_new_account">حساب کاربری جدیدی بسازید</string> <string name="create_new_account">حساب کاربری جدیدی بسازید</string>
</resources> </resources>

View File

@ -9,4 +9,8 @@
<string name="magic_create_text_fixed">Vous avez été invité à %1$s. Un nom dutilisateur a déjà été choisi pour vous. Nous allons vous guider à travers le processus de création dun compte.\nVous pourrez communiquer avec les utilisateurs des autres fournisseurs en leur donnant votre adresse XMPP complète.</string> <string name="magic_create_text_fixed">Vous avez été invité à %1$s. Un nom dutilisateur a déjà été choisi pour vous. Nous allons vous guider à travers le processus de création dun compte.\nVous pourrez communiquer avec les utilisateurs des autres fournisseurs en leur donnant votre adresse XMPP complète.</string>
<string name="your_server_invitation">Votre invitation au serveur</string> <string name="your_server_invitation">Votre invitation au serveur</string>
<string name="improperly_formatted_provisioning">Code de provisionnement mal formaté</string> <string name="improperly_formatted_provisioning">Code de provisionnement mal formaté</string>
<string name="tap_share_button_send_invite">Appuyez sur le bouton partager pour envoyer à votre contact une invitation pour %1$s</string>
<string name="if_contact_is_nearby_use_qr">Si vos contacts sont à votre côté, ils peuvent aussi scanner le code ci dessous pour accepter votre invitation</string>
<string name="easy_invite_share_text">Rejoignez %1$set discutez avec moi : %2$s</string>
<string name="share_invite_with">Partager une invitation avec ...</string>
</resources> </resources>

View File

@ -9,4 +9,8 @@
<string name="magic_create_text_fixed">Convidáronte a %1$s. Escollemos un nome de usuaria por ti. Guiarémoste no proceso de crear unha conta.\nPoderás comunicarte con usuarias de outros provedores cando lles digas o teu enderezo XMPP completo.</string> <string name="magic_create_text_fixed">Convidáronte a %1$s. Escollemos un nome de usuaria por ti. Guiarémoste no proceso de crear unha conta.\nPoderás comunicarte con usuarias de outros provedores cando lles digas o teu enderezo XMPP completo.</string>
<string name="your_server_invitation">O convite do teu servidor</string> <string name="your_server_invitation">O convite do teu servidor</string>
<string name="improperly_formatted_provisioning">Código de aprovisionamento con formato non válido</string> <string name="improperly_formatted_provisioning">Código de aprovisionamento con formato non válido</string>
<string name="tap_share_button_send_invite">Toca no botón compartir para convidar ó teu contacto a %1$s.</string>
<string name="if_contact_is_nearby_use_qr">Se o contacto está preto de ti, pode escanear o código inferior para aceptar o teu convite.</string>
<string name="easy_invite_share_text">Únete a %1$s e conversa conmigo: %2$s</string>
<string name="share_invite_with">Enviar convite a...</string>
</resources> </resources>

View File

@ -8,4 +8,9 @@
<string name="magic_create_text_on_x">Meghívást kapott a(z) %1$s kiszolgálóra. Végig fogjuk vezetni egy fiók létrehozásának folyamatán.\nHa a(z) %1$s kiszolgálót választja szolgáltatóként, akkor képes lesz más szolgáltatók felhasználóival is kommunikálni, ha megadja nekik a teljes XMPP-címét.</string> <string name="magic_create_text_on_x">Meghívást kapott a(z) %1$s kiszolgálóra. Végig fogjuk vezetni egy fiók létrehozásának folyamatán.\nHa a(z) %1$s kiszolgálót választja szolgáltatóként, akkor képes lesz más szolgáltatók felhasználóival is kommunikálni, ha megadja nekik a teljes XMPP-címét.</string>
<string name="magic_create_text_fixed">Meghívást kapott a(z) %1$s kiszolgálóra. Már kiválasztottak Önnek egy felhasználónevet. Végig fogjuk vezetni egy fiók létrehozásának folyamatán.\nKépes lesz más szolgáltatók felhasználóival is kommunikálni, ha megadja nekik a teljes XMPP-címét.</string> <string name="magic_create_text_fixed">Meghívást kapott a(z) %1$s kiszolgálóra. Már kiválasztottak Önnek egy felhasználónevet. Végig fogjuk vezetni egy fiók létrehozásának folyamatán.\nKépes lesz más szolgáltatók felhasználóival is kommunikálni, ha megadja nekik a teljes XMPP-címét.</string>
<string name="your_server_invitation">Az Ön kiszolgálómeghívása</string> <string name="your_server_invitation">Az Ön kiszolgálómeghívása</string>
<string name="improperly_formatted_provisioning">Helytelenül formázott kiépítési kód</string>
<string name="tap_share_button_send_invite">Koppintson a megosztás gombra, hogy meghívót küldjön a partnerének erre: %1$s.</string>
<string name="if_contact_is_nearby_use_qr">Ha a partnere a közelben van, akkor a meghívás elfogadásához leolvashatja a lenti kódot.</string>
<string name="easy_invite_share_text">Csatlakozzon ehhez: %1$s, és csevegjen velem: %2$s</string>
<string name="share_invite_with">Meghívás megosztása…</string>
</resources> </resources>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pick_a_server">Pilih XMPP server anda</string>
<string name="use_chat.sum7.eu">Gunakan chat.sum7.eu</string>
<string name="create_new_account">Buat akun baru</string>
<string name="do_you_have_an_account">Anda sudah memiliki akun XMPP? Ini mungkin terjadi jika Anda sudah menggunakan aplikasi XMPP yang berbeda atau pernah menggunakan Conversations sebelumnya. Jika tidak, Anda dapat membuat akun XMPP baru. \ NPetunjuk: Beberapa penyedia layanan email juga menyediakan akun XMPP.</string>
<string name="server_select_text">XMPP adalah jaringan penyedia pesan instan independen. Anda dapat menggunakan aplikasi ini dengan server XMPP pilihan Anda. \ NNamun demi kenyamanan Anda, kami permudah untuk membuat akun di Conversations.im¹; provider yang sangat cocok digunakan dengan Conversations.</string>
<string name="magic_create_text_on_x">Anda telah diundang ke %1$s. Kami akan memandu Anda melalui proses pembuatan akun. \nSaat memilih %1$s sebagai penyedia, Anda akan dapat berkomunikasi dengan pengguna provider lain dengan memberikan alamat XMPP lengkap Anda kepada mereka.</string>
<string name="magic_create_text_fixed">Anda telah diundang ke%1$s. Username telah dipilihkan untuk Anda. Kami akan memandu Anda melalui proses pembuatan akun. \nAnda dapat berkomunikasi dengan pengguna provider lain dengan memberi mereka alamat XMPP lengkap Anda.</string>
<string name="your_server_invitation">Undangan server Anda</string>
<string name="improperly_formatted_provisioning">Kode provisioning tidak diformat dengan benar</string>
<string name="tap_share_button_send_invite">Klik tombol bagikan untuk mengirim undangan ke kontak Anda %1$s.</string>
<string name="if_contact_is_nearby_use_qr">Jika kontak Anda di dekat Anda, mereka juga dapat memindai kode di bawah ini untuk menerima undangan Anda</string>
<string name="easy_invite_share_text">Bergabung %1$s dan mengobrol dengan saya: %2$s</string>
<string name="share_invite_with">Bagikan undangan dengan...</string>
</resources>

View File

@ -1,14 +1,18 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="pick_a_server">Scegli il tuo provider XMPP</string> <string name="pick_a_server">Scegli il tuo fornitore XMPP</string>
<string name="use_chat.sum7.eu">Usa chat.sum7.eu</string> <string name="use_chat.sum7.eu">Usa chat.sum7.eu</string>
<string name="create_new_account">Crea un nuovo account</string> <string name="create_new_account">Crea un nuovo profilo</string>
<string name="do_you_have_an_account">Possiedi già un account XMPP? Questo succede se stai già usando un diverso client XMPP o hai già usato prima Conversations. In caso negativo puoi creare un account XMPP adesso. <string name="do_you_have_an_account">Possiedi già un profilo XMPP? Questo succede se stai già usando un diverso client XMPP o hai già usato prima Conversations. In caso negativo puoi creare un profilo XMPP adesso.
Suggerimento: alcuni provider di email forniscono anche un account XMPP.</string> Suggerimento: alcuni provider di email forniscono anche un account XMPP.</string>
<string name="server_select_text">XMPP è una rete di instant messaging indipendente dal provider. Puoi usare questo client con qualsiasi server XMPP. <string name="server_select_text">XMPP è una rete di messaggistica istantanea indipendente dal fornitore. Puoi usare questo client con qualsiasi server XMPP.
In ogni caso per facilitare puoi creare facilmente un account su chat.sum7.eu, un provider pensato apposta per essere usato con Conversations.</string> In ogni caso per facilitare puoi creare facilmente un account su chat.sum7.eu, un fornitore pensato apposta per essere usato con Conversations.</string>
<string name="magic_create_text_on_x">Sei stato invitato su %1$s. Ti guideremo nel procedimento per creare un account.\nQuando scegli %1$s come fornitore sarai in grado di comunicare con utenti di altri fornitori dando loro l\'indirizzo XMPP completo.</string> <string name="magic_create_text_on_x">Hai ricevuto un invito per %1$s. Ti guideremo nel procedimento per creare un profilo.\nQuando scegli %1$s come fornitore sarai in grado di comunicare con utenti di altri fornitori dando loro l\'indirizzo XMPP completo.</string>
<string name="magic_create_text_fixed">Sei stato invitato su %1$s. È già stato scelto un nome utente per te. Ti guideremo nel procedimento per creare un account.\nSarai in grado di comunicare con utenti di altri fornitori dando loro l\'indirizzo XMPP completo.</string> <string name="magic_create_text_fixed">Hai ricevuto un invito per %1$s. È già stato scelto un nome utente per te. Ti guideremo nel procedimento per creare un profilo.\nSarai in grado di comunicare con utenti di altri fornitori dando loro l\'indirizzo XMPP completo.</string>
<string name="your_server_invitation">Il tuo invito al server</string> <string name="your_server_invitation">Il tuo invito al server</string>
<string name="improperly_formatted_provisioning">Codice di approvvigionamento formattato male</string> <string name="improperly_formatted_provisioning">Codice di approvvigionamento formattato male</string>
<string name="tap_share_button_send_invite">Tocca il pulsante condividi per inviare al contatto un invito per %1$s.</string>
<string name="if_contact_is_nearby_use_qr">Se il contatto è vicino, può anche scansionare il codice sottostante per accettare il tuo invito.</string>
<string name="easy_invite_share_text">Unisciti a %1$s e chatta con me: %2$s</string>
<string name="share_invite_with">Condividi invito con...</string>
</resources> </resources>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pick_a_server">XMPP プロバイダーを選択してください</string>
<string name="use_chat.sum7.eu">chat.sum7.eu を利用する</string>
<string name="create_new_account">新規アカウントを作成</string>
<string name="do_you_have_an_account">XMPP アカウントをお持ちですか?既にほかの XMPP クライアントを利用しているか、 Conversations を利用したことがある場合はこちら。初めての方は、今すぐ新規 XMPP アカウントを作成できます。\nヒント: e メールのプロバイダーが XMPP アカウントも提供している場合があります。</string>
<string name="server_select_text">XMPP は、プロバイダーに依存しないインスタントメッセージのプロトコルです。 XMPP サーバーならどこでも、このクライアントを使用することができます。\nよろしければ、 Conversations に最適化されたプロバイダー chat.sum7.eu で簡単にアカウントを作成することもできます。</string>
<string name="magic_create_text_on_x">%1$s へ招待されました。アカウント作成手順をご案内します。 \n%1$s をプロバイダーに選択してほかのプロバイダーのユーザーと会話するには、 XMPP のフルアドレスを相手にお知らせください。</string>
<string name="magic_create_text_fixed">%1$s へ招待されました。ユーザー名は既に選択されています。アカウント作成手順をご案内します。 \nほかのプロバイダーのユーザーと会話するには、 XMPP のフルアドレスを相手にお知らせください。</string>
<string name="your_server_invitation">サーバーの招待</string>
<string name="improperly_formatted_provisioning">仮コードの書式が不正です</string>
<string name="tap_share_button_send_invite">共有ボタンを叩いて、連絡先の %1$s に招待を送信する。</string>
<string name="if_contact_is_nearby_use_qr">あなたの連絡先が近くにいる場合は、下のコードをスキャンして、あなたの招待を受け取ることもできます。</string>
<string name="easy_invite_share_text">%1$s に参加して私とお話しましょう: %2$s</string>
<string name="share_invite_with">…で招待を共有</string>
</resources>

View File

@ -8,4 +8,9 @@
<string name="magic_create_text_on_x">Zostałeś zaproszony do %1$s. Poprowadzimy ciebie przez proces tworzenia konta.\nWybierając %1$s jako dostawcę będziesz mógł komunikować się z innymi użytkownikami podając swój pełny adres XMPP.</string> <string name="magic_create_text_on_x">Zostałeś zaproszony do %1$s. Poprowadzimy ciebie przez proces tworzenia konta.\nWybierając %1$s jako dostawcę będziesz mógł komunikować się z innymi użytkownikami podając swój pełny adres XMPP.</string>
<string name="magic_create_text_fixed">Zostałeś zaproszony do %1$s. Nazwa użytkownika została już dla ciebie wybrana. Poprowadzimy ciebie przez proces tworzenia konta.\nBęziesz mógł komunikować się z innymi użytkownikami podając swój adres XMPP.</string> <string name="magic_create_text_fixed">Zostałeś zaproszony do %1$s. Nazwa użytkownika została już dla ciebie wybrana. Poprowadzimy ciebie przez proces tworzenia konta.\nBęziesz mógł komunikować się z innymi użytkownikami podając swój adres XMPP.</string>
<string name="your_server_invitation">Zaproszenie twojego serwera</string> <string name="your_server_invitation">Zaproszenie twojego serwera</string>
<string name="improperly_formatted_provisioning">Niepoprawnie sformatowany kod zaopatrywania</string>
<string name="tap_share_button_send_invite">Użyj przycisku udostępniania aby wysłać swojemu kontaktowi zaproszenie do %1$s.</string>
<string name="if_contact_is_nearby_use_qr">Jeśli twój kontakt jest blisko może przeskanować kod poniżej aby zaakceptować twoje zaproszenie.</string>
<string name="easy_invite_share_text">Dołącz do %1$s aby porozmawiać ze mną: %2$s</string>
<string name="share_invite_with">Udostępnij zaproszenie...</string>
</resources> </resources>

View File

@ -9,4 +9,8 @@
<string name="magic_create_text_fixed">Você foi convidado para %1$s. Um nome de usuário já foi escolhido para você. Nós iremos guiá-lo ao longo do processo de criação de uma conta.\nVocê conseguirá se comunicar com usuários de outros provedores dando a eles seu endereço XMPP completo.</string> <string name="magic_create_text_fixed">Você foi convidado para %1$s. Um nome de usuário já foi escolhido para você. Nós iremos guiá-lo ao longo do processo de criação de uma conta.\nVocê conseguirá se comunicar com usuários de outros provedores dando a eles seu endereço XMPP completo.</string>
<string name="your_server_invitation">Seu convite do servidor</string> <string name="your_server_invitation">Seu convite do servidor</string>
<string name="improperly_formatted_provisioning">Código de provisionamento formatado de maneira imprópria</string> <string name="improperly_formatted_provisioning">Código de provisionamento formatado de maneira imprópria</string>
<string name="tap_share_button_send_invite">Toque no botão compartilhar para enviar, para seu contato, um convite para %1$s.</string>
<string name="if_contact_is_nearby_use_qr">Se seu contato estiver por perto, ele também pode escanear o código abaixo para aceitar seu convite.</string>
<string name="easy_invite_share_text">Junte-se a %1$s e converse comigo: %2$s</string>
<string name="share_invite_with">Compartilhe o convite com...</string>
</resources> </resources>

View File

@ -9,4 +9,8 @@
<string name="magic_create_text_fixed">Ați fost invitați la %1$s. Un nume de utilizator a fost deja ales pentru dumneavoastră. Vă vom ghida prin procesul de creare al unui cont.\nVeți putea comunica cu utilizatorii altor furnizori oferindu-le adresa dumneavoastră completă XMPP.</string> <string name="magic_create_text_fixed">Ați fost invitați la %1$s. Un nume de utilizator a fost deja ales pentru dumneavoastră. Vă vom ghida prin procesul de creare al unui cont.\nVeți putea comunica cu utilizatorii altor furnizori oferindu-le adresa dumneavoastră completă XMPP.</string>
<string name="your_server_invitation">Invitația serverului dumneavoastră</string> <string name="your_server_invitation">Invitația serverului dumneavoastră</string>
<string name="improperly_formatted_provisioning">Cod de acces formatat necorespunzător</string> <string name="improperly_formatted_provisioning">Cod de acces formatat necorespunzător</string>
<string name="tap_share_button_send_invite">Atingeți butonul de partajare pentru a trimite contactului o invitație la %1$s.</string>
<string name="if_contact_is_nearby_use_qr">Dacă e în apropiere, contactul poate scana codul de mai jos pentru a vă accepta invitația.</string>
<string name="easy_invite_share_text">Alătură-te %1$s și discută cu mine: %2$s</string>
<string name="share_invite_with">Partajează invitația cu…</string>
</resources> </resources>

View File

@ -9,4 +9,8 @@
<string name="magic_create_text_fixed">Вас пригласили на %1$s. Вам уже назначили имя пользователя. Мы проведём вас через процесс создания аккаунта. Этот аккаунт позволит вам общаться с пользователями и на этом, и на других серверах, используя ваш полный XMPP-адрес.</string> <string name="magic_create_text_fixed">Вас пригласили на %1$s. Вам уже назначили имя пользователя. Мы проведём вас через процесс создания аккаунта. Этот аккаунт позволит вам общаться с пользователями и на этом, и на других серверах, используя ваш полный XMPP-адрес.</string>
<string name="your_server_invitation">Ваше приглашение</string> <string name="your_server_invitation">Ваше приглашение</string>
<string name="improperly_formatted_provisioning">Неправильный формат кода</string> <string name="improperly_formatted_provisioning">Неправильный формат кода</string>
<string name="tap_share_button_send_invite">Нажмите кнопку «Поделиться», чтобы отправить вашему контакту приглашение в %1$s.</string>
<string name="if_contact_is_nearby_use_qr">Если ваш контакт находится поблизости, он также может отсканировать приведенный ниже код, чтобы принять ваше приглашение.</string>
<string name="easy_invite_share_text">Присоединяйтесь к %1$s и пообщайтесь со мной: %2$s</string>
<string name="share_invite_with">Поделиться приглашением с…</string>
</resources> </resources>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pick_a_server">Vyberte si svojho XMPP poskytovateľa</string>
<string name="use_chat.sum7.eu">Použiť chat.sum7.eu</string>
<string name="create_new_account">Vytvoriť nové konto</string>
<string name="do_you_have_an_account">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á.</string>
<string name="server_select_text">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 chat.sum7.eu; poskytovateľ špeciálne vhodný na používanie s Conversations.</string>
<string name="magic_create_text_on_x">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.</string>
<string name="magic_create_text_fixed">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.</string>
<string name="tap_share_button_send_invite">Ťuknite na tlačidlo zdieľať na odoslanie pozvánky do %1$s vášmu kontaktu.</string>
<string name="if_contact_is_nearby_use_qr">Ak je váš kontakt blízko, na prijatie vašej pozvánky si môže nasnímať kód nižšie.</string>
<string name="easy_invite_share_text">Pripojte sa k %1$sa rozprávajte sa so mnou: %2$s</string>
<string name="share_invite_with">Zdieľať pozvánku s...</string>
</resources>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pick_a_server">Одаберите вашег ИксМПП провајдера</string>
<string name="use_chat.sum7.eu">Користи chat.sum7.eu</string>
<string name="create_new_account">Направи нови налог</string>
<string name="do_you_have_an_account">Да ли већ имате ИксМПП налог? Извесно је да га имате ако користите неки ИксМПП клијент или сте раније користили Конверзацију. Ако немате, сада можете направити нови ИксМПП налог.\nСавет: неки поштански провајдери такође омогућавају и ИксМПП налоге.</string>
<string name="server_select_text">ИксМПП је мрежа брзих порука, независна од провајдера. Овај клијент можете користити уз било који сервер по вашем избору.\nДа бисмо вам олакшали, омогућили смо креирање налога на chat.sum7.eu; провајдеру специјално прилаг.ођеном за коришћење уз Конверзацију</string>
<string name="your_server_invitation">Ваша серверска позивница</string>
</resources>

View File

@ -1,5 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="pick_a_server">Välj din XMPP leverantör</string>
<string name="use_chat.sum7.eu">Använd chat.sum7.eu</string> <string name="use_chat.sum7.eu">Använd chat.sum7.eu</string>
<string name="create_new_account">Skapa nytt konto</string> <string name="create_new_account">Skapa nytt konto</string>
<string name="your_server_invitation">Din server inbjudan</string>
<string name="share_invite_with">Dela inbjudan med...</string>
</resources> </resources>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pick_a_server">XMPP sağlayıcınızı seçin</string>
<string name="use_chat.sum7.eu">chat.sum7.eu kullan</string>
<string name="create_new_account">Yeni hesap oluştur</string>
<string name="do_you_have_an_account">Zaten bir XMPP hesabınız var mı? Bunun sebebi, zaten başka bir XMPP istemcisi kullanıyor oluşunuz veya Conversations\'ı önceden kullanmış olmanız olabilir. Eğer durum bu değilse şimdi yeni bir XMPP hesabı oluşturabilirsiniz.\nİpucu: Bağzı e-posta sağlayıcıları da XMPP hesapları kullanabilir.</string>
<string name="server_select_text">XMPP; anlık yazışmalar için bağımsız bir sağlayıcıdır. Bu istemciyi istediğiniz herhangi bir XMPP sunucusu ile birlikte kullanabilirsiniz.\nAncak kullanım rahatlığı adına sizin için chat.sum7.eu; Conversations için özellikle tasarlanmış bir sağlayıcıda hesap açmanızı kolaylaştırdık.</string>
<string name="magic_create_text_on_x">%1$s sağlayıcısına davet edildiniz. Sizi hesap oluşturulması konusunda yönlendireceğiz.\n%1$s bir sağlayıcı olark seçildiğinde, başka sağlayıcılar kullanan kullanıcılarla, onlara tam XMPP adresinizi vererek iletişim kurabileceksiniz.</string>
<string name="magic_create_text_fixed">%1$s sağlayıcısına davet edildiniz. Sizin için zaten bir kullanıcı adı seçildi. Sizi hesap oluşturulması konusunda yönlendireceğiz.\nBaşka sağlayıcılar kullanan kullanıcılarla, onlara tam XMPP adresinizi vererek iletişim kurabileceksiniz.</string>
<string name="your_server_invitation">Sunucu davetiyeniz</string>
<string name="improperly_formatted_provisioning">Yanlış ayarlanmış düzenleme kodu</string>
<string name="tap_share_button_send_invite">Kişinize, %1$s grubuna davet etmek için Paylaş düğmesine basın.</string>
<string name="if_contact_is_nearby_use_qr">Kişiniz yakınınızda ise, aşağıdaki kodu tarayak daveti kabul edebilirler.</string>
<string name="easy_invite_share_text">%1$s grubuna katıl ve benimle sohpet et: %2$s</string>
<string name="share_invite_with">Daveti şununla paylaş...</string>
</resources>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="pick_a_server">Chọn nhà cung cấp XMPP của bạn</string>
<string name="use_chat.sum7.eu">Sử dụng chat.sum7.eu</string>
<string name="create_new_account">Tạo tài khoản mới</string>
<string name="do_you_have_an_account">Bạn đã có tài khoản XMPP chưa? Điều này có thể đúng nếu bạn đang dùng một ứng dụng khách cho XMPP khác hoặc đã sử dụng Conversations trước đó. Nếu không, bạn có thể tạo tài khoản XMPP mới ngay bây giờ.\nGợi ý: Một số nhà cung cấp email cũng cung cấp tài khoản XMPP.</string>
<string name="server_select_text">XMPP là một mạng nhắn tin ngay lập tức không phụ thuộc vào nhà cung cấp. Bạn có thể sử dụng ứng dụng khách này với bất kỳ máy chủ XMPP nào mà bạn chọn.\nTuy nhiên, vì sự thuận tiện của bạn, chúng tôi đã làm cho việc tạo tài khoản trên chat.sum7.eu được dễ dàng; một nhà cung cấp đặc biệt phù hợp với việc sử dụng Conversations.</string>
<string name="magic_create_text_on_x">Bạn đã được mời vào %1$s. Chúng tôi sẽ hướng dẫn bạn trong quá trình tạo tài khoản.\nKhi chọn %1$s là 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.</string>
<string name="magic_create_text_fixed">Bạn đã được mời vào %1$s. Một tên người dùng đã được chọn sẵn cho bạn. Chúng tôi sẽ hướng dẫn bạn trong quá trình tạo tài khoản.\nBạ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.</string>
<string name="your_server_invitation">Lời mời vào máy chủ của bạn</string>
<string name="improperly_formatted_provisioning">Mã cung cấp không được định dạng đúng</string>
<string name="tap_share_button_send_invite">Nhấn nút chia sẻ để gửi lời mời vào %1$s đến liên hệ của bạn.</string>
<string name="if_contact_is_nearby_use_qr">Nếu liên hệ của bạn ở gần đây, họ cũng có thể quét mã ở dưới để chấp nhận lời mời của bạn.</string>
<string name="easy_invite_share_text">Hãy tham gia vào %1$s và trò chuyện với tôi: %2$s</string>
<string name="share_invite_with">Chia sẻ lời mời với...</string>
</resources>

View File

@ -9,4 +9,8 @@
<string name="magic_create_text_fixed">您已受邀参加%1$s。 已经为您选择了一个用户名。 我们将指导您完成创建帐户的过程。\n您可以通过向其他提供商的用户提供完整的XMPP地址来与他们进行交流。</string> <string name="magic_create_text_fixed">您已受邀参加%1$s。 已经为您选择了一个用户名。 我们将指导您完成创建帐户的过程。\n您可以通过向其他提供商的用户提供完整的XMPP地址来与他们进行交流。</string>
<string name="your_server_invitation">你的服务器邀请</string> <string name="your_server_invitation">你的服务器邀请</string>
<string name="improperly_formatted_provisioning">格式不正确的配置代码</string> <string name="improperly_formatted_provisioning">格式不正确的配置代码</string>
<string name="tap_share_button_send_invite">点击分享按钮向您的联系人发送加入 %1$s 的邀请。</string>
<string name="if_contact_is_nearby_use_qr">如果你的联系人在附近,他们也可以扫描下面的代码来接受你的邀请。</string>
<string name="easy_invite_share_text">加入 %1$s 和我聊天:%2$s</string>
<string name="share_invite_with">分享邀请</string>
</resources> </resources>

View File

@ -9,4 +9,8 @@
<string name="magic_create_text_fixed">You have been invited to %1$s. A username has already been picked for you. We will guide you through the process of creating an account.\nYou will be able to communicate with users of other providers by giving them your full XMPP address.</string> <string name="magic_create_text_fixed">You have been invited to %1$s. A username has already been picked for you. We will guide you through the process of creating an account.\nYou will be able to communicate with users of other providers by giving them your full XMPP address.</string>
<string name="your_server_invitation">Your server invitation</string> <string name="your_server_invitation">Your server invitation</string>
<string name="improperly_formatted_provisioning">Improperly formatted provisioning code</string> <string name="improperly_formatted_provisioning">Improperly formatted provisioning code</string>
<string name="tap_share_button_send_invite">Tap the share button to send your contact an invitation to %1$s.</string>
<string name="if_contact_is_nearby_use_qr">If your contact is nearby, they can also scan the code below to accept your invitation.</string>
<string name="easy_invite_share_text">Join %1$s and chat with me: %2$s</string>
<string name="share_invite_with">Share invite with…</string>
</resources> </resources>

View File

@ -2,14 +2,9 @@ package eu.siacs.conversations.ui.service;
import android.content.Context; import android.content.Context;
import android.os.Build; import android.os.Build;
import android.support.text.emoji.EmojiCompat; import androidx.emoji.text.EmojiCompat;
import android.support.text.emoji.FontRequestEmojiCompatConfig; import androidx.emoji.text.FontRequestEmojiCompatConfig;
import android.support.text.emoji.bundled.BundledEmojiCompatConfig; import androidx.emoji.bundled.BundledEmojiCompatConfig;
import android.support.v4.provider.FontRequest;
import android.util.Log;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
public class EmojiService { public class EmojiService {

View File

@ -7,6 +7,9 @@
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_CONTACTS" /> <uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.READ_PROFILE" /> <uses-permission android:name="android.permission.READ_PROFILE" />
<uses-permission
android:name="android.permission.READ_PHONE_STATE"
android:maxSdkVersion="22" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" /> <uses-permission android:name="android.permission.WAKE_LOCK" />
@ -36,12 +39,6 @@
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" /> <uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" /> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission
android:name="android.permission.READ_PHONE_STATE"
tools:node="remove" />
<uses-sdk tools:overrideLibrary="net.ypresto.androidtranscoder" />
<uses-feature <uses-feature
android:name="android.hardware.camera" android:name="android.hardware.camera"
android:required="false" /> android:required="false" />
@ -55,15 +52,18 @@
<application <application
android:allowBackup="false" android:allowBackup="true"
android:fullBackupContent="@xml/backup_content"
android:appCategory="social" android:appCategory="social"
android:hardwareAccelerated="true" android:hardwareAccelerated="true"
android:icon="@mipmap/new_launcher" android:icon="@mipmap/new_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:largeHeap="true"
android:networkSecurityConfig="@xml/network_security_configuration" android:networkSecurityConfig="@xml/network_security_configuration"
android:requestLegacyExternalStorage="true"
android:theme="@style/ConversationsTheme" android:theme="@style/ConversationsTheme"
tools:replace="android:label" tools:replace="android:label"
tools:targetApi="o"> tools:targetApi="q">
<meta-data <meta-data
android:name="com.google.android.gms.car.application" android:name="com.google.android.gms.car.application"
@ -131,7 +131,7 @@
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" /> <data android:scheme="https" />
<data android:host="conversations.im" /> <data android:host="chat.sum7.eu" />
<data android:pathPrefix="/i/" /> <data android:pathPrefix="/i/" />
<data android:pathPrefix="/j/" /> <data android:pathPrefix="/j/" />
</intent-filter> </intent-filter>
@ -143,6 +143,14 @@
<data android:scheme="imto" /> <data android:scheme="imto" />
<data android:host="jabber" /> <data android:host="jabber" />
</intent-filter> </intent-filter>
<intent-filter>
<action android:name="android.intent.action.SENDTO" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="imto" />
<data android:host="xmpp" />
</intent-filter>
</activity> </activity>
<activity <activity
android:name=".ui.StartConversationActivity" android:name=".ui.StartConversationActivity"
@ -265,7 +273,7 @@
</service> </service>
<provider <provider
android:name="android.support.v4.content.FileProvider" android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.files" android:authorities="${applicationId}.files"
android:exported="false" android:exported="false"
android:grantUriPermissions="true"> android:grantUriPermissions="true">

View File

@ -3,11 +3,14 @@ package eu.siacs.conversations;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.net.Uri; import android.net.Uri;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Locale;
import eu.siacs.conversations.xmpp.chatstate.ChatState; import eu.siacs.conversations.crypto.XmppDomainVerifier;
import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.Jid;
import eu.siacs.conversations.xmpp.chatstate.ChatState;
public final class Config { public final class Config {
private static final int UNENCRYPTED = 1; private static final int UNENCRYPTED = 1;
@ -33,7 +36,7 @@ public final class Config {
return (ENCRYPTION_MASK & (ENCRYPTION_MASK - 1)) != 0; return (ENCRYPTION_MASK & (ENCRYPTION_MASK - 1)) != 0;
} }
public static final String LOGTAG = BuildConfig.LOGTAG; public static final String LOGTAG = BuildConfig.APP_NAME.toLowerCase(Locale.US);
public static final Jid BUG_REPORTS = Jid.of("bugs@chat.sum7.eu"); public static final Jid BUG_REPORTS = Jid.of("bugs@chat.sum7.eu");
public static final Uri HELP = Uri.parse("https://sum7.eu/chat"); public static final Uri HELP = Uri.parse("https://sum7.eu/chat");
@ -101,6 +104,7 @@ public final class Config {
public static final boolean REMOVE_BROKEN_DEVICES = false; public static final boolean REMOVE_BROKEN_DEVICES = false;
public static final boolean OMEMO_PADDING = false; public static final boolean OMEMO_PADDING = false;
public static final boolean PUT_AUTH_TAG_INTO_KEY = true; public static final boolean PUT_AUTH_TAG_INTO_KEY = true;
public static final boolean AUTOMATICALLY_COMPLETE_SESSIONS = true;
public static final boolean USE_BOOKMARKS2 = false; public static final boolean USE_BOOKMARKS2 = false;
@ -114,11 +118,12 @@ public final class Config {
public static final boolean ENCRYPT_ON_HTTP_UPLOADED = false; 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 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 public static final boolean ONLY_INTERNAL_STORAGE = false; //use internal storage instead of sdcard to save attachments
public static final boolean IGNORE_ID_REWRITE_IN_MUC = true; public static final boolean IGNORE_ID_REWRITE_IN_MUC = true;
public static final boolean MUC_LEAVE_BEFORE_JOIN = true; public static final boolean MUC_LEAVE_BEFORE_JOIN = false;
public static final boolean USE_LMC_VERSION_1_1 = true; public static final boolean USE_LMC_VERSION_1_1 = true;
@ -173,7 +178,14 @@ public final class Config {
//if the contacts domain matches one of the following domains OMEMO wont be turned on automatically //if the contacts domain matches one of the following domains OMEMO wont be turned on automatically
//can be used for well known, widely used gateways //can be used for well known, widely used gateways
public static final List<String> CONTACT_DOMAINS = Collections.singletonList("cheogram.com"); private static final List<String> CONTACT_DOMAINS = Arrays.asList(
"cheogram.com",
"*.covid.monal.im"
);
public static boolean matchesContactDomain(final String domain) {
return XmppDomainVerifier.matchDomain(domain, CONTACT_DOMAINS);
}
} }
private Config() { private Config() {
@ -188,4 +200,9 @@ public final class Config {
public final static float LOCATION_FIX_SPACE_DELTA = 10; // m public final static float LOCATION_FIX_SPACE_DELTA = 10; // m
public final static int LOCATION_FIX_SIGNIFICANT_TIME_DELTA = 1000 * 60 * 2; // ms public final static int LOCATION_FIX_SIGNIFICANT_TIME_DELTA = 1000 * 60 * 2; // ms
} }
// How deep nested quotes should be displayed. '2' means one quote nested in another.
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 = 1;
} }

View File

@ -17,6 +17,21 @@ import eu.siacs.conversations.xmpp.Jid;
public class JabberIdContact extends AbstractPhoneContact { public class JabberIdContact extends AbstractPhoneContact {
private static final String[] PROJECTION = new String[]{ContactsContract.Data._ID,
ContactsContract.Data.DISPLAY_NAME,
ContactsContract.Data.PHOTO_URI,
ContactsContract.Data.LOOKUP_KEY,
ContactsContract.CommonDataKinds.Im.DATA
};
private static final String SELECTION = ContactsContract.Data.MIMETYPE + "=? AND (" + ContactsContract.CommonDataKinds.Im.PROTOCOL + "=? or (" + ContactsContract.CommonDataKinds.Im.PROTOCOL + "=? and lower(" + ContactsContract.CommonDataKinds.Im.CUSTOM_PROTOCOL + ")=?))";
private static final String[] SELECTION_ARGS = {
ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE,
String.valueOf(ContactsContract.CommonDataKinds.Im.PROTOCOL_JABBER),
String.valueOf(ContactsContract.CommonDataKinds.Im.PROTOCOL_CUSTOM),
"xmpp"
};
private final Jid jid; private final Jid jid;
private JabberIdContact(Cursor cursor) throws IllegalArgumentException { private JabberIdContact(Cursor cursor) throws IllegalArgumentException {
@ -36,38 +51,26 @@ public class JabberIdContact extends AbstractPhoneContact {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && context.checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && context.checkSelfPermission(Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) {
return Collections.emptyMap(); return Collections.emptyMap();
} }
final String[] PROJECTION = new String[]{ContactsContract.Data._ID, try (final Cursor cursor = context.getContentResolver().query(ContactsContract.Data.CONTENT_URI, PROJECTION, SELECTION, SELECTION_ARGS, null)) {
ContactsContract.Data.DISPLAY_NAME, if (cursor == null) {
ContactsContract.Data.PHOTO_URI,
ContactsContract.Data.LOOKUP_KEY,
ContactsContract.CommonDataKinds.Im.DATA};
final String SELECTION = "(" + ContactsContract.Data.MIMETYPE + "=\""
+ ContactsContract.CommonDataKinds.Im.CONTENT_ITEM_TYPE
+ "\") AND (" + ContactsContract.CommonDataKinds.Im.PROTOCOL
+ "=\"" + ContactsContract.CommonDataKinds.Im.PROTOCOL_JABBER
+ "\")";
final Cursor cursor;
try {
cursor = context.getContentResolver().query(ContactsContract.Data.CONTENT_URI, PROJECTION, SELECTION, null, null);
} catch (Exception e) {
return Collections.emptyMap(); return Collections.emptyMap();
} }
final HashMap<Jid, JabberIdContact> contacts = new HashMap<>(); final HashMap<Jid, JabberIdContact> contacts = new HashMap<>();
while (cursor != null && cursor.moveToNext()) { while (cursor.moveToNext()) {
try { try {
final JabberIdContact contact = new JabberIdContact(cursor); final JabberIdContact contact = new JabberIdContact(cursor);
final JabberIdContact preexisting = contacts.put(contact.getJid(), contact); final JabberIdContact preexisting = contacts.put(contact.getJid(), contact);
if (preexisting == null || preexisting.rating() < contact.rating()) { if (preexisting == null || preexisting.rating() < contact.rating()) {
contacts.put(contact.getJid(), contact); contacts.put(contact.getJid(), contact);
} }
} catch (IllegalArgumentException e) { } catch (final IllegalArgumentException e) {
Log.d(Config.LOGTAG, "unable to create jabber id contact"); Log.d(Config.LOGTAG, "unable to create jabber id contact");
} }
} }
if (cursor != null) {
cursor.close();
}
return contacts; return contacts;
} catch (final Exception e) {
Log.d(Config.LOGTAG, "unable to query", e);
return Collections.emptyMap();
}
} }
} }

View File

@ -1,10 +1,11 @@
package eu.siacs.conversations.crypto; package eu.siacs.conversations.crypto;
import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSession;
public interface DomainHostnameVerifier extends HostnameVerifier { public interface DomainHostnameVerifier extends HostnameVerifier {
boolean verify(String domain, String hostname, SSLSession sslSession); boolean verify(String domain, String hostname, SSLSession sslSession) throws SSLPeerUnverifiedException;
} }

View File

@ -14,7 +14,6 @@ import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.URL;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
@ -209,7 +208,7 @@ public class PgpDecryptionService {
message.setRelativeFilePath(path); message.setRelativeFilePath(path);
} }
} }
URL url = message.getFileParams().url; final String url = message.getFileParams().url;
mXmppConnectionService.getFileBackend().updateFileParams(message, url); mXmppConnectionService.getFileBackend().updateFileParams(message, url);
message.setEncryption(Message.ENCRYPTION_DECRYPTED); message.setEncryption(Message.ENCRYPTION_DECRYPTED);
mXmppConnectionService.updateMessage(message); mXmppConnectionService.updateMessage(message);

View File

@ -2,9 +2,14 @@ package eu.siacs.conversations.crypto;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.content.Intent; import android.content.Intent;
import android.support.annotation.StringRes;
import android.util.Log; import android.util.Log;
import androidx.annotation.StringRes;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import org.openintents.openpgp.OpenPgpError; import org.openintents.openpgp.OpenPgpError;
import org.openintents.openpgp.OpenPgpSignatureResult; import org.openintents.openpgp.OpenPgpSignatureResult;
import org.openintents.openpgp.util.OpenPgpApi; import org.openintents.openpgp.util.OpenPgpApi;
@ -17,6 +22,7 @@ import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.ArrayList;
import eu.siacs.conversations.Config; import eu.siacs.conversations.Config;
import eu.siacs.conversations.R; import eu.siacs.conversations.R;
@ -28,10 +34,11 @@ import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.persistance.FileBackend; import eu.siacs.conversations.persistance.FileBackend;
import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.ui.UiCallback; import eu.siacs.conversations.ui.UiCallback;
import eu.siacs.conversations.utils.AsciiArmor;
public class PgpEngine { public class PgpEngine {
private OpenPgpApi api; private final OpenPgpApi api;
private XmppConnectionService mXmppConnectionService; private final XmppConnectionService mXmppConnectionService;
public PgpEngine(OpenPgpApi api, XmppConnectionService service) { public PgpEngine(OpenPgpApi api, XmppConnectionService service) {
this.api = api; this.api = api;
@ -65,7 +72,7 @@ public class PgpEngine {
params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true); params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true);
String body; String body;
if (message.hasFileOnRemoteHost()) { if (message.hasFileOnRemoteHost()) {
body = message.getFileParams().url.toString(); body = message.getFileParams().url;
} else { } else {
body = message.getBody(); body = message.getBody();
} }
@ -76,14 +83,14 @@ public class PgpEngine {
case OpenPgpApi.RESULT_CODE_SUCCESS: case OpenPgpApi.RESULT_CODE_SUCCESS:
try { try {
os.flush(); os.flush();
StringBuilder encryptedMessageBody = new StringBuilder(); final ArrayList<String> encryptedMessageBody = new ArrayList<>();
String[] lines = os.toString().split("\n"); final String[] lines = os.toString().split("\n");
for (int i = 2; i < lines.length - 1; ++i) { for (int i = 2; i < lines.length - 1; ++i) {
if (!lines[i].contains("Version")) { if (!lines[i].contains("Version")) {
encryptedMessageBody.append(lines[i].trim()); encryptedMessageBody.add(lines[i].trim());
} }
} }
message.setEncryptedBody(encryptedMessageBody.toString()); message.setEncryptedBody(Joiner.on('\n').join(encryptedMessageBody));
message.setEncryption(Message.ENCRYPTION_DECRYPTED); message.setEncryption(Message.ENCRYPTION_DECRYPTED);
mXmppConnectionService.sendMessage(message); mXmppConnectionService.sendMessage(message);
callback.success(message); callback.success(message);
@ -146,36 +153,26 @@ public class PgpEngine {
} }
} }
public long fetchKeyId(Account account, String status, String signature) { public long fetchKeyId(final Account account, final String status, final String signature) {
if ((signature == null) || (api == null)) { if (signature == null || api == null) {
return 0; return 0;
} }
if (status == null) { final Intent params = new Intent();
status = "";
}
final StringBuilder pgpSig = new StringBuilder();
pgpSig.append("-----BEGIN PGP SIGNED MESSAGE-----");
pgpSig.append('\n');
pgpSig.append('\n');
pgpSig.append(status);
pgpSig.append('\n');
pgpSig.append("-----BEGIN PGP SIGNATURE-----");
pgpSig.append('\n');
pgpSig.append('\n');
pgpSig.append(signature.replace("\n", "").trim());
pgpSig.append('\n');
pgpSig.append("-----END PGP SIGNATURE-----");
Intent params = new Intent();
params.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY); params.setAction(OpenPgpApi.ACTION_DECRYPT_VERIFY);
params.putExtra(OpenPgpApi.EXTRA_REQUEST_ASCII_ARMOR, true); try {
InputStream is = new ByteArrayInputStream(pgpSig.toString().getBytes()); params.putExtra(OpenPgpApi.RESULT_DETACHED_SIGNATURE, AsciiArmor.decode(signature));
ByteArrayOutputStream os = new ByteArrayOutputStream(); } catch (final Exception e) {
Intent result = api.executeApi(params, is, os); Log.d(Config.LOGTAG, "unable to parse signature", e);
return 0;
}
final InputStream is = new ByteArrayInputStream(Strings.nullToEmpty(status).getBytes());
final ByteArrayOutputStream os = new ByteArrayOutputStream();
final Intent result = api.executeApi(params, is, os);
switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, switch (result.getIntExtra(OpenPgpApi.RESULT_CODE,
OpenPgpApi.RESULT_CODE_ERROR)) { OpenPgpApi.RESULT_CODE_ERROR)) {
case OpenPgpApi.RESULT_CODE_SUCCESS: case OpenPgpApi.RESULT_CODE_SUCCESS:
OpenPgpSignatureResult sigResult = result final OpenPgpSignatureResult sigResult = result.getParcelableExtra(OpenPgpApi.RESULT_SIGNATURE);
.getParcelableExtra(OpenPgpApi.RESULT_SIGNATURE); //TODO unsure that sigResult.getResult() is either 1, 2 or 3
if (sigResult != null) { if (sigResult != null) {
return sigResult.getKeyId(); return sigResult.getKeyId();
} else { } else {
@ -222,18 +219,17 @@ public class PgpEngine {
api.executeApiAsync(params, is, os, result -> { api.executeApiAsync(params, is, os, result -> {
switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) { switch (result.getIntExtra(OpenPgpApi.RESULT_CODE, 0)) {
case OpenPgpApi.RESULT_CODE_SUCCESS: case OpenPgpApi.RESULT_CODE_SUCCESS:
StringBuilder signatureBuilder = new StringBuilder(); final ArrayList<String> signature = new ArrayList<>();
try { try {
os.flush(); os.flush();
String[] lines = os.toString().split("\n");
boolean sig = false; boolean sig = false;
for (String line : lines) { for (final String line : Splitter.on('\n').split(os.toString())) {
if (sig) { if (sig) {
if (line.contains("END PGP SIGNATURE")) { if (line.contains("END PGP SIGNATURE")) {
sig = false; sig = false;
} else { } else {
if (!line.contains("Version")) { if (!line.contains("Version")) {
signatureBuilder.append(line.trim()); signature.add(line.trim());
} }
} }
} }
@ -245,7 +241,7 @@ public class PgpEngine {
callback.error(R.string.openpgp_error, null); callback.error(R.string.openpgp_error, null);
return; return;
} }
callback.success(signatureBuilder.toString()); callback.success(Joiner.on('\n').join(signature));
return; return;
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED: case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
callback.userInputRequired(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), status); callback.userInputRequired(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), status);

View File

@ -1,9 +1,10 @@
package eu.siacs.conversations.crypto; package eu.siacs.conversations.crypto;
import android.os.Build;
import android.util.Log; import android.util.Log;
import android.util.Pair; import android.util.Pair;
import com.google.common.collect.ImmutableList;
import org.bouncycastle.asn1.ASN1Primitive; import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.DERIA5String; import org.bouncycastle.asn1.DERIA5String;
import org.bouncycastle.asn1.DERTaggedObject; import org.bouncycastle.asn1.DERTaggedObject;
@ -16,17 +17,20 @@ import org.bouncycastle.asn1.x500.style.IETFUtils;
import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder; import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
import java.io.IOException; import java.io.IOException;
import java.net.IDN;
import java.security.cert.Certificate; import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSession;
public class XmppDomainVerifier implements DomainHostnameVerifier { public class XmppDomainVerifier {
private static final String LOGTAG = "XmppDomainVerifier"; private static final String LOGTAG = "XmppDomainVerifier";
@ -71,8 +75,8 @@ public class XmppDomainVerifier implements DomainHostnameVerifier {
} }
} }
private static boolean matchDomain(String needle, List<String> haystack) { public static boolean matchDomain(final String needle, final List<String> haystack) {
for (String entry : haystack) { for (final String entry : haystack) {
if (entry.startsWith("*.")) { if (entry.startsWith("*.")) {
int offset = 0; int offset = 0;
while (offset < needle.length()) { while (offset < needle.length()) {
@ -80,16 +84,13 @@ public class XmppDomainVerifier implements DomainHostnameVerifier {
if (i < 0) { if (i < 0) {
break; break;
} }
Log.d(LOGTAG, "comparing " + needle.substring(i) + " and " + entry.substring(1));
if (needle.substring(i).equalsIgnoreCase(entry.substring(1))) { if (needle.substring(i).equalsIgnoreCase(entry.substring(1))) {
Log.d(LOGTAG, "domain " + needle + " matched " + entry);
return true; return true;
} }
offset = i + 1; offset = i + 1;
} }
} else { } else {
if (entry.equalsIgnoreCase(needle)) { if (entry.equalsIgnoreCase(needle)) {
Log.d(LOGTAG, "domain " + needle + " matched " + entry);
return true; return true;
} }
} }
@ -97,25 +98,42 @@ public class XmppDomainVerifier implements DomainHostnameVerifier {
return false; return false;
} }
@Override public boolean verify(final String unicodeDomain, final String unicodeHostname, SSLSession sslSession) throws SSLPeerUnverifiedException {
public boolean verify(String domain, String hostname, SSLSession sslSession) { final String domain = IDN.toASCII(unicodeDomain);
try { final String hostname = unicodeHostname == null ? null : IDN.toASCII(unicodeHostname);
Certificate[] chain = sslSession.getPeerCertificates(); final Certificate[] chain = sslSession.getPeerCertificates();
if (chain.length == 0 || !(chain[0] instanceof X509Certificate)) { if (chain.length == 0 || !(chain[0] instanceof X509Certificate)) {
return false; return false;
} }
X509Certificate certificate = (X509Certificate) chain[0]; final X509Certificate certificate = (X509Certificate) chain[0];
final List<String> commonNames = getCommonNames(certificate); final List<String> commonNames = getCommonNames(certificate);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && isSelfSigned(certificate)) { if (isSelfSigned(certificate)) {
if (commonNames.size() == 1 && matchDomain(domain, commonNames)) { if (commonNames.size() == 1 && matchDomain(domain, commonNames)) {
Log.d(LOGTAG, "accepted CN in self signed cert as work around for " + domain); Log.d(LOGTAG, "accepted CN in self signed cert as work around for " + domain);
return true; return true;
} }
} }
Collection<List<?>> alternativeNames = certificate.getSubjectAlternativeNames(); try {
List<String> xmppAddrs = new ArrayList<>(); final ValidDomains validDomains = parseValidDomains(certificate);
List<String> srvNames = new ArrayList<>(); Log.d(LOGTAG, "searching for " + domain + " in srvNames: " + validDomains.srvNames + " xmppAddrs: " + validDomains.xmppAddrs + " domains:" + validDomains.domains);
List<String> domains = new ArrayList<>(); if (hostname != null) {
Log.d(LOGTAG, "also trying to verify hostname " + hostname);
}
return validDomains.xmppAddrs.contains(domain)
|| validDomains.srvNames.contains("_xmpp-client." + domain)
|| matchDomain(domain, validDomains.domains)
|| (hostname != null && matchDomain(hostname, validDomains.domains));
} catch (final Exception e) {
return false;
}
}
public static ValidDomains parseValidDomains(final X509Certificate certificate) throws CertificateParsingException {
final List<String> commonNames = getCommonNames(certificate);
final Collection<List<?>> alternativeNames = certificate.getSubjectAlternativeNames();
final List<String> xmppAddrs = new ArrayList<>();
final List<String> srvNames = new ArrayList<>();
final List<String> domains = new ArrayList<>();
if (alternativeNames != null) { if (alternativeNames != null) {
for (List<?> san : alternativeNames) { for (List<?> san : alternativeNames) {
final Integer type = (Integer) san.get(0); final Integer type = (Integer) san.get(0);
@ -144,16 +162,26 @@ public class XmppDomainVerifier implements DomainHostnameVerifier {
if (srvNames.size() == 0 && xmppAddrs.size() == 0 && domains.size() == 0) { if (srvNames.size() == 0 && xmppAddrs.size() == 0 && domains.size() == 0) {
domains.addAll(commonNames); domains.addAll(commonNames);
} }
Log.d(LOGTAG, "searching for " + domain + " in srvNames: " + srvNames + " xmppAddrs: " + xmppAddrs + " domains:" + domains); return new ValidDomains(xmppAddrs, srvNames, domains);
if (hostname != null) {
Log.d(LOGTAG, "also trying to verify hostname " + hostname);
} }
return xmppAddrs.contains(domain)
|| srvNames.contains("_xmpp-client." + domain) public static final class ValidDomains {
|| matchDomain(domain, domains) final List<String> xmppAddrs;
|| (hostname != null && matchDomain(hostname, domains)); final List<String> srvNames;
} catch (Exception e) { final List<String> domains;
return false;
private ValidDomains(List<String> xmppAddrs, List<String> srvNames, List<String> domains) {
this.xmppAddrs = xmppAddrs;
this.srvNames = srvNames;
this.domains = domains;
}
public List<String> all() {
ImmutableList.Builder<String> all = new ImmutableList.Builder<>();
all.addAll(xmppAddrs);
all.addAll(srvNames);
all.addAll(domains);
return all.build();
} }
} }
@ -165,9 +193,4 @@ public class XmppDomainVerifier implements DomainHostnameVerifier {
return false; return false;
} }
} }
@Override
public boolean verify(String domain, SSLSession sslSession) {
return verify(domain, null, sslSession);
}
} }

View File

@ -2,18 +2,26 @@ package eu.siacs.conversations.crypto.axolotl;
import android.os.Bundle; import android.os.Bundle;
import android.security.KeyChain; import android.security.KeyChain;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log; import android.util.Log;
import android.util.Pair; import android.util.Pair;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
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.bouncycastle.jce.provider.BouncyCastleProvider;
import org.whispersystems.libsignal.SignalProtocolAddress;
import org.whispersystems.libsignal.IdentityKey; import org.whispersystems.libsignal.IdentityKey;
import org.whispersystems.libsignal.IdentityKeyPair; import org.whispersystems.libsignal.IdentityKeyPair;
import org.whispersystems.libsignal.InvalidKeyException; import org.whispersystems.libsignal.InvalidKeyException;
import org.whispersystems.libsignal.InvalidKeyIdException; import org.whispersystems.libsignal.InvalidKeyIdException;
import org.whispersystems.libsignal.SessionBuilder; import org.whispersystems.libsignal.SessionBuilder;
import org.whispersystems.libsignal.SignalProtocolAddress;
import org.whispersystems.libsignal.UntrustedIdentityException; import org.whispersystems.libsignal.UntrustedIdentityException;
import org.whispersystems.libsignal.ecc.ECPublicKey; import org.whispersystems.libsignal.ecc.ECPublicKey;
import org.whispersystems.libsignal.state.PreKeyBundle; import org.whispersystems.libsignal.state.PreKeyBundle;
@ -48,12 +56,18 @@ import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.CryptoHelper;
import eu.siacs.conversations.utils.SerialSingleThreadExecutor; import eu.siacs.conversations.utils.SerialSingleThreadExecutor;
import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xml.Namespace;
import eu.siacs.conversations.xmpp.Jid;
import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded; import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded;
import eu.siacs.conversations.xmpp.OnIqPacketReceived; import eu.siacs.conversations.xmpp.OnIqPacketReceived;
import eu.siacs.conversations.xmpp.jingle.OmemoVerification;
import eu.siacs.conversations.xmpp.jingle.OmemoVerifiedRtpContentMap;
import eu.siacs.conversations.xmpp.jingle.RtpContentMap;
import eu.siacs.conversations.xmpp.jingle.stanzas.IceUdpTransportInfo;
import eu.siacs.conversations.xmpp.jingle.stanzas.OmemoVerifiedIceUdpTransportInfo;
import eu.siacs.conversations.xmpp.pep.PublishOptions; import eu.siacs.conversations.xmpp.pep.PublishOptions;
import eu.siacs.conversations.xmpp.stanzas.IqPacket; import eu.siacs.conversations.xmpp.stanzas.IqPacket;
import eu.siacs.conversations.xmpp.stanzas.MessagePacket; import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
import eu.siacs.conversations.xmpp.Jid;
public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
@ -85,9 +99,9 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
private int numPublishTriesOnEmptyPep = 0; private int numPublishTriesOnEmptyPep = 0;
private boolean pepBroken = false; private boolean pepBroken = false;
private int lastDeviceListNotificationHash = 0; private int lastDeviceListNotificationHash = 0;
private Set<XmppAxolotlSession> postponedSessions = new HashSet<>(); //sessions stored here will receive after mam catchup treatment private final Set<XmppAxolotlSession> postponedSessions = new HashSet<>(); //sessions stored here will receive after mam catchup treatment
private Set<SignalProtocolAddress> postponedHealing = new HashSet<>(); //addresses stored here will need a healing notification after mam catchup private final Set<SignalProtocolAddress> postponedHealing = new HashSet<>(); //addresses stored here will need a healing notification after mam catchup
private AtomicBoolean changeAccessMode = new AtomicBoolean(false); private final AtomicBoolean changeAccessMode = new AtomicBoolean(false);
public AxolotlService(Account account, XmppConnectionService connectionService) { public AxolotlService(Account account, XmppConnectionService connectionService) {
if (account == null || connectionService == null) { if (account == null || connectionService == null) {
@ -724,16 +738,22 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
axolotlStore.setFingerprintStatus(fingerprint, status); axolotlStore.setFingerprintStatus(fingerprint, status);
} }
private void verifySessionWithPEP(final XmppAxolotlSession session) { private ListenableFuture<XmppAxolotlSession> verifySessionWithPEP(final XmppAxolotlSession session) {
Log.d(Config.LOGTAG, "trying to verify fresh session (" + session.getRemoteAddress().getName() + ") with pep"); Log.d(Config.LOGTAG, "trying to verify fresh session (" + session.getRemoteAddress().getName() + ") with pep");
final SignalProtocolAddress address = session.getRemoteAddress(); final SignalProtocolAddress address = session.getRemoteAddress();
final IdentityKey identityKey = session.getIdentityKey(); final IdentityKey identityKey = session.getIdentityKey();
final Jid jid;
try { try {
IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveVerificationForDevice(Jid.of(address.getName()), address.getDeviceId()); jid = Jid.of(address.getName());
mXmppConnectionService.sendIqPacket(account, packet, new OnIqPacketReceived() { } catch (final IllegalArgumentException e) {
@Override fetchStatusMap.put(address, FetchStatus.SUCCESS);
public void onIqPacketReceived(Account account, IqPacket packet) { finishBuildingSessionsFromPEP(address);
Pair<X509Certificate[], byte[]> verification = mXmppConnectionService.getIqParser().verification(packet); return Futures.immediateFuture(session);
}
final SettableFuture<XmppAxolotlSession> future = SettableFuture.create();
final IqPacket packet = mXmppConnectionService.getIqGenerator().retrieveVerificationForDevice(jid, address.getDeviceId());
mXmppConnectionService.sendIqPacket(account, packet, (account, response) -> {
Pair<X509Certificate[], byte[]> verification = mXmppConnectionService.getIqParser().verification(response);
if (verification != null) { if (verification != null) {
try { try {
Signature verifier = Signature.getInstance("sha256WithRSA"); Signature verifier = Signature.getInstance("sha256WithRSA");
@ -750,13 +770,14 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
Bundle information = CryptoHelper.extractCertificateInformation(verification.first[0]); Bundle information = CryptoHelper.extractCertificateInformation(verification.first[0]);
try { try {
final String cn = information.getString("subject_cn"); final String cn = information.getString("subject_cn");
final Jid jid = Jid.of(address.getName()); final Jid jid1 = Jid.of(address.getName());
Log.d(Config.LOGTAG, "setting common name for " + jid + " to " + cn); Log.d(Config.LOGTAG, "setting common name for " + jid1 + " to " + cn);
account.getRoster().getContact(jid).setCommonName(cn); account.getRoster().getContact(jid1).setCommonName(cn);
} catch (final IllegalArgumentException ignored) { } catch (final IllegalArgumentException ignored) {
//ignored //ignored
} }
finishBuildingSessionsFromPEP(address); finishBuildingSessionsFromPEP(address);
future.set(session);
return; return;
} catch (Exception e) { } catch (Exception e) {
Log.d(Config.LOGTAG, "could not verify certificate"); Log.d(Config.LOGTAG, "could not verify certificate");
@ -770,12 +791,9 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
} }
fetchStatusMap.put(address, FetchStatus.SUCCESS); fetchStatusMap.put(address, FetchStatus.SUCCESS);
finishBuildingSessionsFromPEP(address); finishBuildingSessionsFromPEP(address);
} future.set(session);
}); });
} catch (IllegalArgumentException e) { return future;
fetchStatusMap.put(address, FetchStatus.SUCCESS);
finishBuildingSessionsFromPEP(address);
}
} }
private void finishBuildingSessionsFromPEP(final SignalProtocolAddress address) { private void finishBuildingSessionsFromPEP(final SignalProtocolAddress address) {
@ -891,22 +909,23 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
} }
} }
private void buildSessionFromPEP(final SignalProtocolAddress address) { private ListenableFuture<XmppAxolotlSession> buildSessionFromPEP(final SignalProtocolAddress address) {
buildSessionFromPEP(address, null); return buildSessionFromPEP(address, null);
} }
private void buildSessionFromPEP(final SignalProtocolAddress address, OnSessionBuildFromPep callback) { private ListenableFuture<XmppAxolotlSession> buildSessionFromPEP(final SignalProtocolAddress address, OnSessionBuildFromPep callback) {
final SettableFuture<XmppAxolotlSession> sessionSettableFuture = SettableFuture.create();
Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building new session for " + address.toString()); Log.i(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building new session for " + address.toString());
if (address.equals(getOwnAxolotlAddress())) { if (address.equals(getOwnAxolotlAddress())) {
throw new AssertionError("We should NEVER build a session with ourselves. What happened here?!"); throw new AssertionError("We should NEVER build a session with ourselves. What happened here?!");
} }
final Jid jid = Jid.of(address.getName()); final Jid jid = Jid.of(address.getName());
final boolean oneOfOurs = jid.asBareJid().equals(account.getJid().asBareJid()); final boolean oneOfOurs = jid.asBareJid().equals(account.getJid().asBareJid());
IqPacket bundlesPacket = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(jid, address.getDeviceId()); IqPacket bundlesPacket = mXmppConnectionService.getIqGenerator().retrieveBundlesForDevice(jid, address.getDeviceId());
mXmppConnectionService.sendIqPacket(account, bundlesPacket, (account, packet) -> { mXmppConnectionService.sendIqPacket(account, bundlesPacket, (account, packet) -> {
if (packet.getType() == IqPacket.TYPE.TIMEOUT) { if (packet.getType() == IqPacket.TYPE.TIMEOUT) {
fetchStatusMap.put(address, FetchStatus.TIMEOUT); fetchStatusMap.put(address, FetchStatus.TIMEOUT);
sessionSettableFuture.setException(new CryptoFailedException("Unable to build session. Timeout"));
} else if (packet.getType() == IqPacket.TYPE.RESULT) { } else if (packet.getType() == IqPacket.TYPE.RESULT) {
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received preKey IQ packet, processing..."); Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received preKey IQ packet, processing...");
final IqParser parser = mXmppConnectionService.getIqParser(); final IqParser parser = mXmppConnectionService.getIqParser();
@ -919,6 +938,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
if (callback != null) { if (callback != null) {
callback.onSessionBuildFailed(); callback.onSessionBuildFailed();
} }
sessionSettableFuture.setException(new CryptoFailedException("Unable to build session. IQ Packet Invalid"));
return; return;
} }
Random random = new Random(); Random random = new Random();
@ -930,6 +950,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
if (callback != null) { if (callback != null) {
callback.onSessionBuildFailed(); callback.onSessionBuildFailed();
} }
sessionSettableFuture.setException(new CryptoFailedException("Unable to build session. No suitable PreKey found"));
return; return;
} }
@ -944,7 +965,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, bundle.getIdentityKey()); XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, bundle.getIdentityKey());
sessions.put(address, session); sessions.put(address, session);
if (Config.X509_VERIFICATION) { 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 { } else {
FingerprintStatus status = getFingerprintTrust(CryptoHelper.bytesToHex(bundle.getIdentityKey().getPublicKey().serialize())); FingerprintStatus status = getFingerprintTrust(CryptoHelper.bytesToHex(bundle.getIdentityKey().getPublicKey().serialize()));
FetchStatus fetchStatus; FetchStatus fetchStatus;
@ -960,6 +981,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
if (callback != null) { if (callback != null) {
callback.onSessionBuildSuccessful(); callback.onSessionBuildSuccessful();
} }
sessionSettableFuture.set(session);
} }
} catch (UntrustedIdentityException | InvalidKeyException e) { } catch (UntrustedIdentityException | InvalidKeyException e) {
Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error building session for " + address + ": " Log.e(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Error building session for " + address + ": "
@ -972,6 +994,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
if (callback != null) { if (callback != null) {
callback.onSessionBuildFailed(); callback.onSessionBuildFailed();
} }
sessionSettableFuture.setException(new CryptoFailedException(e));
} }
} else { } else {
fetchStatusMap.put(address, FetchStatus.ERROR); fetchStatusMap.put(address, FetchStatus.ERROR);
@ -985,8 +1008,10 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
if (callback != null) { if (callback != null) {
callback.onSessionBuildFailed(); callback.onSessionBuildFailed();
} }
sessionSettableFuture.setException(new CryptoFailedException("Unable to build session. IQ Packet Error"));
} }
}); });
return sessionSettableFuture;
} }
private void removeFromDeviceAnnouncement(Integer id) { private void removeFromDeviceAnnouncement(Integer id) {
@ -1160,7 +1185,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(account.getJid().asBareJid(), getOwnDeviceId()); final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(account.getJid().asBareJid(), getOwnDeviceId());
final String content; final String content;
if (message.hasFileOnRemoteHost()) { if (message.hasFileOnRemoteHost()) {
content = message.getFileParams().url.toString(); content = message.getFileParams().url;
} else { } else {
content = message.getBody(); content = message.getBody();
} }
@ -1197,6 +1222,154 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
}); });
} }
private OmemoVerifiedIceUdpTransportInfo encrypt(final IceUdpTransportInfo element, final XmppAxolotlSession session) throws CryptoFailedException {
final OmemoVerifiedIceUdpTransportInfo transportInfo = new OmemoVerifiedIceUdpTransportInfo();
transportInfo.setAttributes(element.getAttributes());
for (final Element child : element.getChildren()) {
if ("fingerprint".equals(child.getName()) && Namespace.JINGLE_APPS_DTLS.equals(child.getNamespace())) {
final Element fingerprint = new Element("fingerprint", Namespace.OMEMO_DTLS_SRTP_VERIFICATION);
fingerprint.setAttribute("setup", child.getAttribute("setup"));
fingerprint.setAttribute("hash", child.getAttribute("hash"));
final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(account.getJid().asBareJid(), getOwnDeviceId());
final String content = child.getContent();
axolotlMessage.encrypt(content);
axolotlMessage.addDevice(session, true);
fingerprint.addChild(axolotlMessage.toElement());
transportInfo.addChild(fingerprint);
} else {
transportInfo.addChild(child);
}
}
return transportInfo;
}
public ListenableFuture<OmemoVerifiedPayload<OmemoVerifiedRtpContentMap>> encrypt(final RtpContentMap rtpContentMap, final Jid jid, final int deviceId) {
return Futures.transformAsync(
getSession(jid, deviceId),
session -> encrypt(rtpContentMap, session),
MoreExecutors.directExecutor()
);
}
private ListenableFuture<OmemoVerifiedPayload<OmemoVerifiedRtpContentMap>> encrypt(final RtpContentMap rtpContentMap, final XmppAxolotlSession session) {
if (Config.REQUIRE_RTP_VERIFICATION) {
requireVerification(session);
}
final ImmutableMap.Builder<String, RtpContentMap.DescriptionTransport> descriptionTransportBuilder = new ImmutableMap.Builder<>();
final OmemoVerification omemoVerification = new OmemoVerification();
omemoVerification.setDeviceId(session.getRemoteAddress().getDeviceId());
omemoVerification.setSessionFingerprint(session.getFingerprint());
for (final Map.Entry<String, RtpContentMap.DescriptionTransport> content : rtpContentMap.contents.entrySet()) {
final RtpContentMap.DescriptionTransport descriptionTransport = content.getValue();
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 Futures.immediateFuture(
new OmemoVerifiedPayload<>(
omemoVerification,
new OmemoVerifiedRtpContentMap(rtpContentMap.group, descriptionTransportBuilder.build())
));
}
private ListenableFuture<XmppAxolotlSession> 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 buildSessionFromPEP(address);
}
return Futures.immediateFuture(session);
}
public ListenableFuture<OmemoVerifiedPayload<RtpContentMap>> decrypt(OmemoVerifiedRtpContentMap omemoVerifiedRtpContentMap, final Jid from) {
final ImmutableMap.Builder<String, RtpContentMap.DescriptionTransport> descriptionTransportBuilder = new ImmutableMap.Builder<>();
final OmemoVerification omemoVerification = new OmemoVerification();
final ImmutableList.Builder<ListenableFuture<XmppAxolotlSession>> pepVerificationFutures = new ImmutableList.Builder<>();
for (final Map.Entry<String, RtpContentMap.DescriptionTransport> content : omemoVerifiedRtpContentMap.contents.entrySet()) {
final RtpContentMap.DescriptionTransport descriptionTransport = content.getValue();
final OmemoVerifiedPayload<IceUdpTransportInfo> decryptedTransport;
try {
decryptedTransport = decrypt((OmemoVerifiedIceUdpTransportInfo) descriptionTransport.transport, from, pepVerificationFutures);
} catch (CryptoFailedException e) {
return Futures.immediateFailedFuture(e);
}
omemoVerification.setOrEnsureEqual(decryptedTransport);
descriptionTransportBuilder.put(
content.getKey(),
new RtpContentMap.DescriptionTransport(descriptionTransport.description, decryptedTransport.payload)
);
}
processPostponed();
final ImmutableList<ListenableFuture<XmppAxolotlSession>> 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<IceUdpTransportInfo> decrypt(final OmemoVerifiedIceUdpTransportInfo verifiedIceUdpTransportInfo, final Jid from, ImmutableList.Builder<ListenableFuture<XmppAxolotlSession>> pepVerificationFutures) throws CryptoFailedException {
final IceUdpTransportInfo transportInfo = new IceUdpTransportInfo();
transportInfo.setAttributes(verifiedIceUdpTransportInfo.getAttributes());
final OmemoVerification omemoVerification = new OmemoVerification();
for (final Element child : verifiedIceUdpTransportInfo.getChildren()) {
if ("fingerprint".equals(child.getName()) && Namespace.OMEMO_DTLS_SRTP_VERIFICATION.equals(child.getNamespace())) {
final Element fingerprint = new Element("fingerprint", Namespace.JINGLE_APPS_DTLS);
fingerprint.setAttribute("setup", child.getAttribute("setup"));
fingerprint.setAttribute("hash", child.getAttribute("hash"));
final Element encrypted = child.findChildEnsureSingle(XmppAxolotlMessage.CONTAINERTAG, AxolotlService.PEP_PREFIX);
final XmppAxolotlMessage xmppAxolotlMessage = XmppAxolotlMessage.fromElement(encrypted, from.asBareJid());
final XmppAxolotlSession session = getReceivingSession(xmppAxolotlMessage);
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());
transportInfo.addChild(fingerprint);
} else {
transportInfo.addChild(child);
}
}
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) { public void prepareKeyTransportMessage(final Conversation conversation, final OnMessageCreatedCallback onMessageCreatedCallback) {
executor.execute(new Runnable() { executor.execute(new Runnable() {
@Override @Override
@ -1317,7 +1490,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
} else { } else {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": nothing to flush. Not republishing key"); Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": nothing to flush. Not republishing key");
} }
if (trustedOrPreviouslyResponded(session)) { if (trustedOrPreviouslyResponded(session) && Config.AUTOMATICALLY_COMPLETE_SESSIONS) {
completeSession(session); completeSession(session);
} }
} }
@ -1332,7 +1505,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
final Iterator<XmppAxolotlSession> iterator = postponedSessions.iterator(); final Iterator<XmppAxolotlSession> iterator = postponedSessions.iterator();
while (iterator.hasNext()) { while (iterator.hasNext()) {
final XmppAxolotlSession session = iterator.next(); final XmppAxolotlSession session = iterator.next();
if (trustedOrPreviouslyResponded(session)) { if (trustedOrPreviouslyResponded(session) && Config.AUTOMATICALLY_COMPLETE_SESSIONS) {
completeSession(session); completeSession(session);
} }
iterator.remove(); iterator.remove();
@ -1394,15 +1567,16 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
return keyTransportMessage; return keyTransportMessage;
} }
private void putFreshSession(XmppAxolotlSession session) { private ListenableFuture<XmppAxolotlSession> putFreshSession(XmppAxolotlSession session) {
sessions.put(session); sessions.put(session);
if (Config.X509_VERIFICATION) { if (Config.X509_VERIFICATION) {
if (session.getIdentityKey() != null) { if (session.getIdentityKey() != null) {
verifySessionWithPEP(session); return verifySessionWithPEP(session);
} else { } else {
Log.e(Config.LOGTAG, account.getJid().asBareJid() + ": identity key was empty after reloading for x509 verification"); Log.e(Config.LOGTAG, account.getJid().asBareJid() + ": identity key was empty after reloading for x509 verification");
} }
} }
return Futures.immediateFuture(session);
} }
public enum FetchStatus { public enum FetchStatus {
@ -1564,4 +1738,36 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
} }
} }
} }
public static class OmemoVerifiedPayload<T> {
private final int deviceId;
private final String fingerprint;
private final T payload;
private OmemoVerifiedPayload(OmemoVerification omemoVerification, T payload) {
this.deviceId = omemoVerification.getDeviceId();
this.fingerprint = omemoVerification.getFingerprint();
this.payload = payload;
}
public int getDeviceId() {
return deviceId;
}
public String getFingerprint() {
return fingerprint;
}
public T getPayload() {
return payload;
}
}
public static class NotVerifiedException extends SecurityException {
public NotVerifiedException(String message) {
super(message);
}
}
} }

View File

@ -3,15 +3,15 @@ package eu.siacs.conversations.crypto.axolotl;
import android.util.Log; import android.util.Log;
import android.util.LruCache; import android.util.LruCache;
import org.whispersystems.libsignal.SignalProtocolAddress;
import org.whispersystems.libsignal.IdentityKey; import org.whispersystems.libsignal.IdentityKey;
import org.whispersystems.libsignal.IdentityKeyPair; import org.whispersystems.libsignal.IdentityKeyPair;
import org.whispersystems.libsignal.InvalidKeyIdException; import org.whispersystems.libsignal.InvalidKeyIdException;
import org.whispersystems.libsignal.SignalProtocolAddress;
import org.whispersystems.libsignal.ecc.Curve; import org.whispersystems.libsignal.ecc.Curve;
import org.whispersystems.libsignal.ecc.ECKeyPair; import org.whispersystems.libsignal.ecc.ECKeyPair;
import org.whispersystems.libsignal.state.SignalProtocolStore;
import org.whispersystems.libsignal.state.PreKeyRecord; import org.whispersystems.libsignal.state.PreKeyRecord;
import org.whispersystems.libsignal.state.SessionRecord; import org.whispersystems.libsignal.state.SessionRecord;
import org.whispersystems.libsignal.state.SignalProtocolStore;
import org.whispersystems.libsignal.state.SignedPreKeyRecord; import org.whispersystems.libsignal.state.SignedPreKeyRecord;
import org.whispersystems.libsignal.util.KeyHelper; import org.whispersystems.libsignal.util.KeyHelper;

View File

@ -59,7 +59,7 @@ public class XmppAxolotlMessage {
switch (keyElement.getName()) { switch (keyElement.getName()) {
case KEYTAG: case KEYTAG:
try { try {
Integer recipientId = Integer.parseInt(keyElement.getAttribute(REMOTEID)); int recipientId = Integer.parseInt(keyElement.getAttribute(REMOTEID));
byte[] key = Base64.decode(keyElement.getContent().trim(), Base64.DEFAULT); byte[] key = Base64.decode(keyElement.getContent().trim(), Base64.DEFAULT);
boolean isPreKey = keyElement.getAttributeAsBoolean("prekey"); boolean isPreKey = keyElement.getAttributeAsBoolean("prekey");
this.keys.add(new XmppAxolotlSession.AxolotlKey(recipientId, key, isPreKey)); this.keys.add(new XmppAxolotlSession.AxolotlKey(recipientId, key, isPreKey));
@ -145,7 +145,7 @@ public class XmppAxolotlMessage {
return ciphertext != null; return ciphertext != null;
} }
void encrypt(String plaintext) throws CryptoFailedException { void encrypt(final String plaintext) throws CryptoFailedException {
try { try {
SecretKey secretKey = new SecretKeySpec(innerKey, KEYTYPE); SecretKey secretKey = new SecretKeySpec(innerKey, KEYTYPE);
IvParameterSpec ivSpec = new IvParameterSpec(iv); IvParameterSpec ivSpec = new IvParameterSpec(iv);

View File

@ -1,10 +1,10 @@
package eu.siacs.conversations.crypto.axolotl; package eu.siacs.conversations.crypto.axolotl;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log; import android.util.Log;
import org.whispersystems.libsignal.SignalProtocolAddress; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.whispersystems.libsignal.DuplicateMessageException; import org.whispersystems.libsignal.DuplicateMessageException;
import org.whispersystems.libsignal.IdentityKey; import org.whispersystems.libsignal.IdentityKey;
import org.whispersystems.libsignal.InvalidKeyException; import org.whispersystems.libsignal.InvalidKeyException;
@ -14,6 +14,7 @@ import org.whispersystems.libsignal.InvalidVersionException;
import org.whispersystems.libsignal.LegacyMessageException; import org.whispersystems.libsignal.LegacyMessageException;
import org.whispersystems.libsignal.NoSessionException; import org.whispersystems.libsignal.NoSessionException;
import org.whispersystems.libsignal.SessionCipher; import org.whispersystems.libsignal.SessionCipher;
import org.whispersystems.libsignal.SignalProtocolAddress;
import org.whispersystems.libsignal.UntrustedIdentityException; import org.whispersystems.libsignal.UntrustedIdentityException;
import org.whispersystems.libsignal.protocol.CiphertextMessage; import org.whispersystems.libsignal.protocol.CiphertextMessage;
import org.whispersystems.libsignal.protocol.PreKeySignalMessage; import org.whispersystems.libsignal.protocol.PreKeySignalMessage;

View File

@ -7,6 +7,8 @@ import eu.siacs.conversations.xml.TagWriter;
public class Anonymous extends SaslMechanism { public class Anonymous extends SaslMechanism {
public static final String MECHANISM = "ANONYMOUS";
public Anonymous(TagWriter tagWriter, Account account, SecureRandom rng) { public Anonymous(TagWriter tagWriter, Account account, SecureRandom rng) {
super(tagWriter, account, rng); super(tagWriter, account, rng);
} }
@ -18,7 +20,7 @@ public class Anonymous extends SaslMechanism {
@Override @Override
public String getMechanism() { public String getMechanism() {
return "ANONYMOUS"; return MECHANISM;
} }
@Override @Override

View File

@ -12,6 +12,9 @@ import eu.siacs.conversations.utils.CryptoHelper;
import eu.siacs.conversations.xml.TagWriter; import eu.siacs.conversations.xml.TagWriter;
public class DigestMd5 extends SaslMechanism { public class DigestMd5 extends SaslMechanism {
public static final String MECHANISM = "DIGEST-MD5";
public DigestMd5(final TagWriter tagWriter, final Account account, final SecureRandom rng) { public DigestMd5(final TagWriter tagWriter, final Account account, final SecureRandom rng) {
super(tagWriter, account, rng); super(tagWriter, account, rng);
} }
@ -23,7 +26,7 @@ public class DigestMd5 extends SaslMechanism {
@Override @Override
public String getMechanism() { public String getMechanism() {
return "DIGEST-MD5"; return MECHANISM;
} }
private State state = State.INITIAL; private State state = State.INITIAL;

View File

@ -1,6 +1,7 @@
package eu.siacs.conversations.crypto.sasl; package eu.siacs.conversations.crypto.sasl;
import android.util.Base64; import android.util.Base64;
import java.security.SecureRandom; import java.security.SecureRandom;
import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Account;
@ -8,6 +9,8 @@ import eu.siacs.conversations.xml.TagWriter;
public class External extends SaslMechanism { public class External extends SaslMechanism {
public static final String MECHANISM = "EXTERNAL";
public External(TagWriter tagWriter, Account account, SecureRandom rng) { public External(TagWriter tagWriter, Account account, SecureRandom rng) {
super(tagWriter, account, rng); super(tagWriter, account, rng);
} }
@ -19,7 +22,7 @@ public class External extends SaslMechanism {
@Override @Override
public String getMechanism() { public String getMechanism() {
return "EXTERNAL"; return MECHANISM;
} }
@Override @Override

View File

@ -8,6 +8,9 @@ import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.xml.TagWriter; import eu.siacs.conversations.xml.TagWriter;
public class Plain extends SaslMechanism { public class Plain extends SaslMechanism {
public static final String MECHANISM = "PLAIN";
public Plain(final TagWriter tagWriter, final Account account) { public Plain(final TagWriter tagWriter, final Account account) {
super(tagWriter, account, null); super(tagWriter, account, null);
} }
@ -19,7 +22,7 @@ public class Plain extends SaslMechanism {
@Override @Override
public String getMechanism() { public String getMechanism() {
return "PLAIN"; return MECHANISM;
} }
@Override @Override

View File

@ -52,14 +52,17 @@ public abstract class SaslMechanism {
* The priority is used to pin the authentication mechanism. If authentication fails, it MAY be retried with another * The priority is used to pin the authentication mechanism. If authentication fails, it MAY be retried with another
* mechanism of the same priority, but MUST NOT be tried with a mechanism of lower priority (to prevent downgrade * mechanism of the same priority, but MUST NOT be tried with a mechanism of lower priority (to prevent downgrade
* attacks). * attacks).
*
* @return An arbitrary int representing the priority * @return An arbitrary int representing the priority
*/ */
public abstract int getPriority(); public abstract int getPriority();
public abstract String getMechanism(); public abstract String getMechanism();
public String getClientFirstMessage() { public String getClientFirstMessage() {
return ""; return "";
} }
public String getResponse(final String challenge) throws AuthenticationException { public String getResponse(final String challenge) throws AuthenticationException {
return ""; return "";
} }

View File

@ -1,53 +1,74 @@
package eu.siacs.conversations.crypto.sasl; package eu.siacs.conversations.crypto.sasl;
import android.annotation.TargetApi;
import android.os.Build;
import android.util.Base64; import android.util.Base64;
import android.util.LruCache;
import com.google.common.base.Objects;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import org.bouncycastle.crypto.Digest; import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.macs.HMac; import org.bouncycastle.crypto.macs.HMac;
import org.bouncycastle.crypto.params.KeyParameter; import org.bouncycastle.crypto.params.KeyParameter;
import java.math.BigInteger;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.security.InvalidKeyException; import java.security.InvalidKeyException;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.concurrent.ExecutionException;
import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.CryptoHelper;
import eu.siacs.conversations.xml.TagWriter; import eu.siacs.conversations.xml.TagWriter;
@TargetApi(Build.VERSION_CODES.HONEYCOMB_MR1)
abstract class ScramMechanism extends SaslMechanism { abstract class ScramMechanism extends SaslMechanism {
// TODO: When channel binding (SCRAM-SHA1-PLUS) is supported in future, generalize this to indicate support and/or usage. // TODO: When channel binding (SCRAM-SHA1-PLUS) is supported in future, generalize this to indicate support and/or usage.
private final static String GS2_HEADER = "n,,"; private final static String GS2_HEADER = "n,,";
private static final byte[] CLIENT_KEY_BYTES = "Client Key".getBytes(); private static final byte[] CLIENT_KEY_BYTES = "Client Key".getBytes();
private static final byte[] SERVER_KEY_BYTES = "Server Key".getBytes(); private static final byte[] SERVER_KEY_BYTES = "Server Key".getBytes();
private static final LruCache<String, KeyPair> CACHE;
static HMac HMAC;
static Digest DIGEST;
static { protected abstract HMac getHMAC();
CACHE = new LruCache<String, KeyPair>(10) {
protected KeyPair create(final String k) { protected abstract Digest getDigest();
// Map keys are "bytesToHex(JID),bytesToHex(password),bytesToHex(salt),iterations,SASL-Mechanism".
// Changing any of these values forces a cache miss. `CryptoHelper.bytesToHex()' private static final Cache<CacheKey, KeyPair> CACHE = CacheBuilder.newBuilder().maximumSize(10).build();
// is applied to prevent commas in the strings breaking things.
final String[] kParts = k.split(",", 5); private static class CacheKey {
try { final String algorithm;
final String password;
final String salt;
final int iterations;
private CacheKey(String algorithm, String password, String salt, int iterations) {
this.algorithm = algorithm;
this.password = password;
this.salt = salt;
this.iterations = iterations;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CacheKey cacheKey = (CacheKey) o;
return iterations == cacheKey.iterations &&
Objects.equal(algorithm, cacheKey.algorithm) &&
Objects.equal(password, cacheKey.password) &&
Objects.equal(salt, cacheKey.salt);
}
@Override
public int hashCode() {
return Objects.hashCode(algorithm, password, salt, iterations);
}
}
private KeyPair getKeyPair(final String password, final String salt, final int iterations) throws ExecutionException {
return CACHE.get(new CacheKey(getHMAC().getAlgorithmName(), password, salt, iterations), () -> {
final byte[] saltedPassword, serverKey, clientKey; final byte[] saltedPassword, serverKey, clientKey;
saltedPassword = hi(CryptoHelper.hexToString(kParts[1]).getBytes(), saltedPassword = hi(password.getBytes(), Base64.decode(salt, Base64.DEFAULT), iterations);
Base64.decode(CryptoHelper.hexToString(kParts[2]), Base64.DEFAULT), Integer.parseInt(kParts[3]));
serverKey = hmac(saltedPassword, SERVER_KEY_BYTES); serverKey = hmac(saltedPassword, SERVER_KEY_BYTES);
clientKey = hmac(saltedPassword, CLIENT_KEY_BYTES); clientKey = hmac(saltedPassword, CLIENT_KEY_BYTES);
return new KeyPair(clientKey, serverKey); return new KeyPair(clientKey, serverKey);
} catch (final InvalidKeyException | NumberFormatException e) { });
return null;
}
}
};
} }
private final String clientNonce; private final String clientNonce;
@ -63,20 +84,21 @@ abstract class ScramMechanism extends SaslMechanism {
clientFirstMessageBare = ""; clientFirstMessageBare = "";
} }
private static synchronized byte[] hmac(final byte[] key, final byte[] input) private byte[] hmac(final byte[] key, final byte[] input) throws InvalidKeyException {
throws InvalidKeyException { final HMac hMac = getHMAC();
HMAC.init(new KeyParameter(key)); hMac.init(new KeyParameter(key));
HMAC.update(input, 0, input.length); hMac.update(input, 0, input.length);
final byte[] out = new byte[HMAC.getMacSize()]; final byte[] out = new byte[hMac.getMacSize()];
HMAC.doFinal(out, 0); hMac.doFinal(out, 0);
return out; return out;
} }
public static synchronized byte[] digest(byte[] bytes) { public byte[] digest(byte[] bytes) {
DIGEST.reset(); final Digest digest = getDigest();
DIGEST.update(bytes, 0, bytes.length); digest.reset();
final byte[] out = new byte[DIGEST.getDigestSize()]; digest.update(bytes, 0, bytes.length);
DIGEST.doFinal(out, 0); final byte[] out = new byte[digest.getDigestSize()];
digest.doFinal(out, 0);
return out; return out;
} }
@ -85,7 +107,7 @@ abstract class ScramMechanism extends SaslMechanism {
* pseudorandom function (PRF) and with dkLen == output length of * pseudorandom function (PRF) and with dkLen == output length of
* HMAC() == output length of H(). * HMAC() == output length of H().
*/ */
private static synchronized byte[] hi(final byte[] key, final byte[] salt, final int iterations) private byte[] hi(final byte[] key, final byte[] salt, final int iterations)
throws InvalidKeyException { throws InvalidKeyException {
byte[] u = hmac(key, CryptoHelper.concatenateByteArrays(salt, CryptoHelper.ONE)); byte[] u = hmac(key, CryptoHelper.concatenateByteArrays(salt, CryptoHelper.ONE));
byte[] out = u.clone(); byte[] out = u.clone();
@ -171,15 +193,10 @@ abstract class ScramMechanism extends SaslMechanism {
final byte[] authMessage = (clientFirstMessageBare + ',' + new String(serverFirstMessage) + ',' final byte[] authMessage = (clientFirstMessageBare + ',' + new String(serverFirstMessage) + ','
+ clientFinalMessageWithoutProof).getBytes(); + clientFinalMessageWithoutProof).getBytes();
// Map keys are "bytesToHex(JID),bytesToHex(password),bytesToHex(salt),iterations,SASL-Mechanism". final KeyPair keys;
final KeyPair keys = CACHE.get( try {
CryptoHelper.bytesToHex(CryptoHelper.saslPrep(account.getJid().asBareJid().toEscapedString()).getBytes()) + "," keys = getKeyPair(CryptoHelper.saslPrep(account.getPassword()), salt, iterationCount);
+ CryptoHelper.bytesToHex(CryptoHelper.saslPrep(account.getPassword()).getBytes()) + "," } catch (ExecutionException e) {
+ CryptoHelper.bytesToHex(salt.getBytes()) + ","
+ iterationCount + ","
+ getMechanism()
);
if (keys == null) {
throw new AuthenticationException("Invalid keys generated"); throw new AuthenticationException("Invalid keys generated");
} }
final byte[] clientSignature; final byte[] clientSignature;

View File

@ -1,5 +1,6 @@
package eu.siacs.conversations.crypto.sasl; package eu.siacs.conversations.crypto.sasl;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.digests.SHA1Digest; import org.bouncycastle.crypto.digests.SHA1Digest;
import org.bouncycastle.crypto.macs.HMac; import org.bouncycastle.crypto.macs.HMac;
@ -9,9 +10,17 @@ import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.xml.TagWriter; import eu.siacs.conversations.xml.TagWriter;
public class ScramSha1 extends ScramMechanism { public class ScramSha1 extends ScramMechanism {
static {
DIGEST = new SHA1Digest(); public static final String MECHANISM = "SCRAM-SHA-1";
HMAC = new HMac(new SHA1Digest());
@Override
protected HMac getHMAC() {
return new HMac(new SHA1Digest());
}
@Override
protected Digest getDigest() {
return new SHA1Digest();
} }
public ScramSha1(final TagWriter tagWriter, final Account account, final SecureRandom rng) { public ScramSha1(final TagWriter tagWriter, final Account account, final SecureRandom rng) {
@ -25,6 +34,6 @@ public class ScramSha1 extends ScramMechanism {
@Override @Override
public String getMechanism() { public String getMechanism() {
return "SCRAM-SHA-1"; return MECHANISM;
} }
} }

View File

@ -1,5 +1,6 @@
package eu.siacs.conversations.crypto.sasl; package eu.siacs.conversations.crypto.sasl;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.digests.SHA256Digest; import org.bouncycastle.crypto.digests.SHA256Digest;
import org.bouncycastle.crypto.macs.HMac; import org.bouncycastle.crypto.macs.HMac;
@ -9,9 +10,17 @@ import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.xml.TagWriter; import eu.siacs.conversations.xml.TagWriter;
public class ScramSha256 extends ScramMechanism { public class ScramSha256 extends ScramMechanism {
static {
DIGEST = new SHA256Digest(); public static final String MECHANISM = "SCRAM-SHA-256";
HMAC = new HMac(new SHA256Digest());
@Override
protected HMac getHMAC() {
return new HMac(new SHA256Digest());
}
@Override
protected Digest getDigest() {
return new SHA256Digest();
} }
public ScramSha256(final TagWriter tagWriter, final Account account, final SecureRandom rng) { public ScramSha256(final TagWriter tagWriter, final Account account, final SecureRandom rng) {
@ -25,6 +34,6 @@ public class ScramSha256 extends ScramMechanism {
@Override @Override
public String getMechanism() { public String getMechanism() {
return "SCRAM-SHA-256"; return MECHANISM;
} }
} }

View File

@ -0,0 +1,39 @@
package eu.siacs.conversations.crypto.sasl;
import org.bouncycastle.crypto.Digest;
import org.bouncycastle.crypto.digests.SHA512Digest;
import org.bouncycastle.crypto.macs.HMac;
import java.security.SecureRandom;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.xml.TagWriter;
public class ScramSha512 extends ScramMechanism {
public static final String MECHANISM = "SCRAM-SHA-512";
@Override
protected HMac getHMAC() {
return new HMac(new SHA512Digest());
}
@Override
protected Digest getDigest() {
return new SHA512Digest();
}
public ScramSha512(final TagWriter tagWriter, final Account account, final SecureRandom rng) {
super(tagWriter, account, rng);
}
@Override
public int getPriority() {
return 30;
}
@Override
public String getMechanism() {
return MECHANISM;
}
}

View File

@ -4,7 +4,8 @@ import android.content.ContentValues;
import android.database.Cursor; import android.database.Cursor;
import android.os.SystemClock; import android.os.SystemClock;
import android.util.Log; import android.util.Log;
import android.util.Pair;
import com.google.common.base.Strings;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
@ -27,9 +28,9 @@ import eu.siacs.conversations.services.AvatarService;
import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.utils.UIHelper;
import eu.siacs.conversations.utils.XmppUri; import eu.siacs.conversations.utils.XmppUri;
import eu.siacs.conversations.xmpp.Jid;
import eu.siacs.conversations.xmpp.XmppConnection; import eu.siacs.conversations.xmpp.XmppConnection;
import eu.siacs.conversations.xmpp.jingle.RtpCapability; import eu.siacs.conversations.xmpp.jingle.RtpCapability;
import eu.siacs.conversations.xmpp.Jid;
public class Account extends AbstractEntity implements AvatarService.Avatarable { public class Account extends AbstractEntity implements AvatarService.Avatarable {
@ -64,7 +65,6 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable
public static final int OPTION_FIXED_USERNAME = 9; public static final int OPTION_FIXED_USERNAME = 9;
private static final String KEY_PGP_SIGNATURE = "pgp_signature"; private static final String KEY_PGP_SIGNATURE = "pgp_signature";
private static final String KEY_PGP_ID = "pgp_id"; private static final String KEY_PGP_ID = "pgp_id";
public final HashSet<Pair<String, String>> inProgressDiscoFetches = new HashSet<>();
protected final JSONObject keys; protected final JSONObject keys;
private final Roster roster = new Roster(this); private final Roster roster = new Roster(this);
private final Collection<Jid> blocklist = new CopyOnWriteArraySet<>(); private final Collection<Jid> blocklist = new CopyOnWriteArraySet<>();
@ -148,7 +148,7 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable
} }
public boolean httpUploadAvailable(long filesize) { public boolean httpUploadAvailable(long filesize) {
return xmppConnection != null && (xmppConnection.getFeatures().httpUpload(filesize) || xmppConnection.getFeatures().p1S3FileTransfer()); return xmppConnection != null && xmppConnection.getFeatures().httpUpload(filesize);
} }
public boolean httpUploadAvailable() { public boolean httpUploadAvailable() {
@ -249,7 +249,7 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable
} }
public String getHostname() { public String getHostname() {
return this.hostname == null ? "" : this.hostname; return Strings.nullToEmpty(this.hostname);
} }
public void setHostname(String hostname) { public void setHostname(String hostname) {
@ -615,6 +615,11 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable
return UIHelper.getColorForName(jid.asBareJid().toString()); return UIHelper.getColorForName(jid.asBareJid().toString());
} }
@Override
public String getAvatarName() {
throw new IllegalStateException("This method should not be called");
}
public enum State { public enum State {
DISABLED(false, false), DISABLED(false, false),
OFFLINE(false), OFFLINE(false),
@ -632,6 +637,7 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable
REGISTRATION_INVALID_TOKEN(true,false), REGISTRATION_INVALID_TOKEN(true,false),
REGISTRATION_PASSWORD_TOO_WEAK(true, false), REGISTRATION_PASSWORD_TOO_WEAK(true, false),
TLS_ERROR, TLS_ERROR,
TLS_ERROR_DOMAIN,
INCOMPATIBLE_SERVER, INCOMPATIBLE_SERVER,
TOR_NOT_AVAILABLE, TOR_NOT_AVAILABLE,
DOWNGRADE_ATTACK, DOWNGRADE_ATTACK,
@ -698,6 +704,8 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable
return R.string.account_status_regis_invalid_token; return R.string.account_status_regis_invalid_token;
case TLS_ERROR: case TLS_ERROR:
return R.string.account_status_tls_error; return R.string.account_status_tls_error;
case TLS_ERROR_DOMAIN:
return R.string.account_status_tls_error_domain;
case INCOMPATIBLE_SERVER: case INCOMPATIBLE_SERVER:
return R.string.account_status_incompatible_server; return R.string.account_status_incompatible_server;
case TOR_NOT_AVAILABLE: case TOR_NOT_AVAILABLE:

View File

@ -1,8 +1,9 @@
package eu.siacs.conversations.entities; package eu.siacs.conversations.entities;
import android.content.Context; import android.content.Context;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.ArrayList; import java.util.ArrayList;
@ -21,7 +22,7 @@ import eu.siacs.conversations.xmpp.Jid;
public class Bookmark extends Element implements ListItem { public class Bookmark extends Element implements ListItem {
private Account account; private final Account account;
private WeakReference<Conversation> conversation; private WeakReference<Conversation> conversation;
private Jid jid; private Jid jid;
@ -248,4 +249,9 @@ public class Bookmark extends Element implements ListItem {
public int getAvatarBackgroundColor() { public int getAvatarBackgroundColor() {
return UIHelper.getColorForName(jid != null ? jid.asBareJid().toString() : getDisplayName()); return UIHelper.getColorForName(jid != null ? jid.asBareJid().toString() : getDisplayName());
} }
@Override
public String getAvatarName() {
return getDisplayName();
}
} }

View File

@ -4,9 +4,12 @@ import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.net.Uri; import android.net.Uri;
import android.support.annotation.NonNull;
import android.text.TextUtils; import android.text.TextUtils;
import androidx.annotation.NonNull;
import com.google.common.base.Strings;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
@ -16,6 +19,7 @@ import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Objects;
import eu.siacs.conversations.Config; import eu.siacs.conversations.Config;
import eu.siacs.conversations.R; import eu.siacs.conversations.R;
@ -25,14 +29,16 @@ import eu.siacs.conversations.services.QuickConversationsService;
import eu.siacs.conversations.utils.JidHelper; import eu.siacs.conversations.utils.JidHelper;
import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.utils.UIHelper;
import eu.siacs.conversations.xml.Element; import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xmpp.pep.Avatar;
import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.Jid;
import eu.siacs.conversations.xmpp.jingle.RtpCapability;
import eu.siacs.conversations.xmpp.pep.Avatar;
public class Contact implements ListItem, Blockable { public class Contact implements ListItem, Blockable {
public static final String TABLENAME = "contacts"; public static final String TABLENAME = "contacts";
public static final String SYSTEMNAME = "systemname"; public static final String SYSTEMNAME = "systemname";
public static final String SERVERNAME = "servername"; public static final String SERVERNAME = "servername";
public static final String PRESENCE_NAME = "presence_name";
public static final String JID = "jid"; public static final String JID = "jid";
public static final String OPTIONS = "options"; public static final String OPTIONS = "options";
public static final String SYSTEMACCOUNT = "systemaccount"; public static final String SYSTEMACCOUNT = "systemaccount";
@ -43,6 +49,7 @@ public class Contact implements ListItem, Blockable {
public static final String LAST_PRESENCE = "last_presence"; public static final String LAST_PRESENCE = "last_presence";
public static final String LAST_TIME = "last_time"; public static final String LAST_TIME = "last_time";
public static final String GROUPS = "groups"; public static final String GROUPS = "groups";
public static final String RTP_CAPABILITY = "rtpCapability";
private String accountUuid; private String accountUuid;
private String systemName; private String systemName;
private String serverName; private String serverName;
@ -61,14 +68,16 @@ public class Contact implements ListItem, Blockable {
private boolean mActive = false; private boolean mActive = false;
private long mLastseen = 0; private long mLastseen = 0;
private String mLastPresence = null; private String mLastPresence = null;
private RtpCapability.Capability rtpCapability;
public Contact(final String account, final String systemName, final String serverName, public Contact(final String account, final String systemName, final String serverName, final String presenceName,
final Jid jid, final int subscription, final String photoUri, final Jid jid, final int subscription, final String photoUri,
final Uri systemAccount, final String keys, final String avatar, final long lastseen, final Uri systemAccount, final String keys, final String avatar, final long lastseen,
final String presence, final String groups) { final String presence, final String groups, final RtpCapability.Capability rtpCapability) {
this.accountUuid = account; this.accountUuid = account;
this.systemName = systemName; this.systemName = systemName;
this.serverName = serverName; this.serverName = serverName;
this.presenceName = presenceName;
this.jid = jid; this.jid = jid;
this.subscription = subscription; this.subscription = subscription;
this.photoUri = photoUri; this.photoUri = photoUri;
@ -92,6 +101,7 @@ public class Contact implements ListItem, Blockable {
} }
this.mLastseen = lastseen; this.mLastseen = lastseen;
this.mLastPresence = presence; this.mLastPresence = presence;
this.rtpCapability = rtpCapability;
} }
public Contact(final Jid jid) { public Contact(final Jid jid) {
@ -116,6 +126,7 @@ public class Contact implements ListItem, Blockable {
return new Contact(cursor.getString(cursor.getColumnIndex(ACCOUNT)), return new Contact(cursor.getString(cursor.getColumnIndex(ACCOUNT)),
cursor.getString(cursor.getColumnIndex(SYSTEMNAME)), cursor.getString(cursor.getColumnIndex(SYSTEMNAME)),
cursor.getString(cursor.getColumnIndex(SERVERNAME)), cursor.getString(cursor.getColumnIndex(SERVERNAME)),
cursor.getString(cursor.getColumnIndex(PRESENCE_NAME)),
jid, jid,
cursor.getInt(cursor.getColumnIndex(OPTIONS)), cursor.getInt(cursor.getColumnIndex(OPTIONS)),
cursor.getString(cursor.getColumnIndex(PHOTOURI)), cursor.getString(cursor.getColumnIndex(PHOTOURI)),
@ -124,10 +135,17 @@ public class Contact implements ListItem, Blockable {
cursor.getString(cursor.getColumnIndex(AVATAR)), cursor.getString(cursor.getColumnIndex(AVATAR)),
cursor.getLong(cursor.getColumnIndex(LAST_TIME)), cursor.getLong(cursor.getColumnIndex(LAST_TIME)),
cursor.getString(cursor.getColumnIndex(LAST_PRESENCE)), cursor.getString(cursor.getColumnIndex(LAST_PRESENCE)),
cursor.getString(cursor.getColumnIndex(GROUPS))); cursor.getString(cursor.getColumnIndex(GROUPS)),
RtpCapability.Capability.of(cursor.getString(cursor.getColumnIndex(RTP_CAPABILITY))));
} }
public String getDisplayName() { public String getDisplayName() {
if (isSelf()) {
final String displayName = account.getDisplayName();
if (!Strings.isNullOrEmpty(displayName)) {
return displayName;
}
}
if (Config.X509_VERIFICATION && !TextUtils.isEmpty(this.commonName)) { if (Config.X509_VERIFICATION && !TextUtils.isEmpty(this.commonName)) {
return this.commonName; return this.commonName;
} else if (!TextUtils.isEmpty(this.systemName)) { } else if (!TextUtils.isEmpty(this.systemName)) {
@ -213,6 +231,7 @@ public class Contact implements ListItem, Blockable {
values.put(ACCOUNT, accountUuid); values.put(ACCOUNT, accountUuid);
values.put(SYSTEMNAME, systemName); values.put(SYSTEMNAME, systemName);
values.put(SERVERNAME, serverName); values.put(SERVERNAME, serverName);
values.put(PRESENCE_NAME, presenceName);
values.put(JID, jid.toString()); values.put(JID, jid.toString());
values.put(OPTIONS, subscription); values.put(OPTIONS, subscription);
values.put(SYSTEMACCOUNT, systemAccount != null ? systemAccount.toString() : null); values.put(SYSTEMACCOUNT, systemAccount != null ? systemAccount.toString() : null);
@ -222,6 +241,7 @@ public class Contact implements ListItem, Blockable {
values.put(LAST_PRESENCE, mLastPresence); values.put(LAST_PRESENCE, mLastPresence);
values.put(LAST_TIME, mLastseen); values.put(LAST_TIME, mLastseen);
values.put(GROUPS, groups.toString()); values.put(GROUPS, groups.toString());
values.put(RTP_CAPABILITY, rtpCapability == null ? null : rtpCapability.toString());
return values; return values;
} }
} }
@ -426,20 +446,18 @@ public class Contact implements ListItem, Blockable {
return getJid().getDomain().toEscapedString(); return getJid().getDomain().toEscapedString();
} }
public boolean setAvatar(Avatar avatar) { public void setAvatar(Avatar avatar) {
return setAvatar(avatar, false); setAvatar(avatar, false);
} }
public boolean setAvatar(Avatar avatar, boolean previouslyOmittedPepFetch) { public void setAvatar(Avatar avatar, boolean previouslyOmittedPepFetch) {
if (this.avatar != null && this.avatar.equals(avatar)) { if (this.avatar != null && this.avatar.equals(avatar)) {
return false; return;
} else { }
if (!previouslyOmittedPepFetch && this.avatar != null && this.avatar.origin == Avatar.Origin.PEP && avatar.origin == Avatar.Origin.VCARD) { if (!previouslyOmittedPepFetch && this.avatar != null && this.avatar.origin == Avatar.Origin.PEP && avatar.origin == Avatar.Origin.VCARD) {
return false; return;
} }
this.avatar = avatar; this.avatar = avatar;
return true;
}
} }
public String getAvatarFilename() { public String getAvatarFilename() {
@ -554,7 +572,26 @@ public class Contact implements ListItem, Blockable {
return UIHelper.getColorForName(jid != null ? jid.asBareJid().toString() : getDisplayName()); return UIHelper.getColorForName(jid != null ? jid.asBareJid().toString() : getDisplayName());
} }
public final class Options { @Override
public String getAvatarName() {
return getDisplayName();
}
public boolean hasAvatarOrPresenceName() {
return (avatar != null && avatar.getFilename() != null) || presenceName != null;
}
public boolean refreshRtpCapability() {
final RtpCapability.Capability previous = this.rtpCapability;
this.rtpCapability = RtpCapability.check(this, false);
return !Objects.equals(previous, this.rtpCapability);
}
public RtpCapability.Capability getRtpCapability() {
return this.rtpCapability == null ? RtpCapability.Capability.NONE : this.rtpCapability;
}
public static final class Options {
public static final int TO = 0; public static final int TO = 0;
public static final int FROM = 1; public static final int FROM = 1;
public static final int ASKING = 2; public static final int ASKING = 2;

View File

@ -2,10 +2,11 @@ package eu.siacs.conversations.entities;
import android.content.ContentValues; import android.content.ContentValues;
import android.database.Cursor; import android.database.Cursor;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils; import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.common.collect.ComparisonChain; import com.google.common.collect.ComparisonChain;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
@ -27,10 +28,11 @@ import eu.siacs.conversations.persistance.DatabaseBackend;
import eu.siacs.conversations.services.AvatarService; import eu.siacs.conversations.services.AvatarService;
import eu.siacs.conversations.services.QuickConversationsService; import eu.siacs.conversations.services.QuickConversationsService;
import eu.siacs.conversations.utils.JidHelper; import eu.siacs.conversations.utils.JidHelper;
import eu.siacs.conversations.utils.MessageUtils;
import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.utils.UIHelper;
import eu.siacs.conversations.xmpp.Jid;
import eu.siacs.conversations.xmpp.chatstate.ChatState; import eu.siacs.conversations.xmpp.chatstate.ChatState;
import eu.siacs.conversations.xmpp.mam.MamReference; import eu.siacs.conversations.xmpp.mam.MamReference;
import eu.siacs.conversations.xmpp.Jid;
import static eu.siacs.conversations.entities.Bookmark.printableValue; import static eu.siacs.conversations.entities.Bookmark.printableValue;
@ -68,12 +70,12 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
public AtomicBoolean messagesLoaded = new AtomicBoolean(true); public AtomicBoolean messagesLoaded = new AtomicBoolean(true);
protected Account account = null; protected Account account = null;
private String draftMessage; private String draftMessage;
private String name; private final String name;
private String contactUuid; private final String contactUuid;
private String accountUuid; private final String accountUuid;
private Jid contactJid; private Jid contactJid;
private int status; private int status;
private long created; private final long created;
private int mode; private int mode;
private JSONObject attributes; private JSONObject attributes;
private Jid nextCounterpart; private Jid nextCounterpart;
@ -142,7 +144,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
} }
final String contact = conversation.getJid().getDomain().toEscapedString(); final String contact = conversation.getJid().getDomain().toEscapedString();
final String account = conversation.getAccount().getServer(); final String account = conversation.getAccount().getServer();
if (Config.OMEMO_EXCEPTIONS.CONTACT_DOMAINS.contains(contact) || Config.OMEMO_EXCEPTIONS.ACCOUNT_DOMAINS.contains(account)) { if (Config.OMEMO_EXCEPTIONS.matchesContactDomain(contact) || Config.OMEMO_EXCEPTIONS.ACCOUNT_DOMAINS.contains(account)) {
return false; return false;
} }
return conversation.isSingleOrPrivateAndNonAnonymous() || conversation.getBooleanAttribute(ATTRIBUTE_FORMERLY_PRIVATE_NON_ANONYMOUS, false); return conversation.isSingleOrPrivateAndNonAnonymous() || conversation.getBooleanAttribute(ATTRIBUTE_FORMERLY_PRIVATE_NON_ANONYMOUS, false);
@ -186,6 +188,18 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
return null; return null;
} }
public int countFailedDeliveries() {
int count = 0;
synchronized (this.messages) {
for(final Message message : this.messages) {
if (message.getStatus() == Message.STATUS_SEND_FAILED) {
++count;
}
}
}
return count;
}
public Message getLastEditableMessage() { public Message getLastEditableMessage() {
synchronized (this.messages) { synchronized (this.messages) {
for (final Message message : Lists.reverse(this.messages)) { for (final Message message : Lists.reverse(this.messages)) {
@ -245,9 +259,22 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
public Message findMessageWithFileAndUuid(final String uuid) { public Message findMessageWithFileAndUuid(final String uuid) {
synchronized (this.messages) { synchronized (this.messages) {
for (final Message message : this.messages) { for (final Message message : this.messages) {
final Transferable transferable = message.getTransferable();
final boolean unInitiatedButKnownSize = MessageUtils.unInitiatedButKnownSize(message);
if (message.getUuid().equals(uuid) if (message.getUuid().equals(uuid)
&& message.getEncryption() != Message.ENCRYPTION_PGP && message.getEncryption() != Message.ENCRYPTION_PGP
&& (message.isFileOrImage() || message.treatAsDownloadable())) { && (message.isFileOrImage() || message.treatAsDownloadable() || unInitiatedButKnownSize || (transferable != null && transferable.getStatus() != Transferable.STATUS_UPLOADING))) {
return message;
}
}
}
return null;
}
public Message findMessageWithUuid(final String uuid) {
synchronized (this.messages) {
for (final Message message : this.messages) {
if (message.getUuid().equals(uuid)) {
return message; return message;
} }
} }
@ -475,7 +502,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
public void setLastClearHistory(long time, String reference) { public void setLastClearHistory(long time, String reference) {
if (reference != null) { if (reference != null) {
setAttribute(ATTRIBUTE_LAST_CLEAR_HISTORY, String.valueOf(time) + ":" + reference); setAttribute(ATTRIBUTE_LAST_CLEAR_HISTORY, time + ":" + reference);
} else { } else {
setAttribute(ATTRIBUTE_LAST_CLEAR_HISTORY, time); setAttribute(ATTRIBUTE_LAST_CLEAR_HISTORY, time);
} }
@ -538,7 +565,15 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
} }
public boolean isRead() { public boolean isRead() {
return (this.messages.size() == 0) || this.messages.get(this.messages.size() - 1).isRead(); synchronized (this.messages) {
for(final Message message : Lists.reverse(this.messages)) {
if (message.isRead() && message.getType() == Message.TYPE_RTP_SESSION) {
continue;
}
return message.isRead();
}
return true;
}
} }
public List<Message> markRead(String upToUuid) { public List<Message> markRead(String upToUuid) {
@ -767,7 +802,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
if (message.getStatus() == Message.STATUS_UNSEND || message.getStatus() == Message.STATUS_SEND) { if (message.getStatus() == Message.STATUS_UNSEND || message.getStatus() == Message.STATUS_SEND) {
String otherBody; String otherBody;
if (message.hasFileOnRemoteHost()) { if (message.hasFileOnRemoteHost()) {
otherBody = message.getFileParams().url.toString(); otherBody = message.getFileParams().url;
} else { } else {
otherBody = message.body; otherBody = message.body;
} }
@ -998,8 +1033,11 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
public int unreadCount() { public int unreadCount() {
synchronized (this.messages) { synchronized (this.messages) {
int count = 0; int count = 0;
for (int i = this.messages.size() - 1; i >= 0; --i) { for(final Message message : Lists.reverse(this.messages)) {
if (this.messages.get(i).isRead()) { if (message.isRead()) {
if (message.getType() == Message.TYPE_RTP_SESSION) {
continue;
}
return count; return count;
} }
++count; ++count;
@ -1066,6 +1104,11 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
return UIHelper.getColorForName(getName().toString()); return UIHelper.getColorForName(getName().toString());
} }
@Override
public String getAvatarName() {
return getName().toString();
}
public interface OnMessageFound { public interface OnMessageFound {
void onMessageFound(final Message message); void onMessageFound(final Message message);
} }

View File

@ -8,20 +8,20 @@ import android.util.Log;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.common.primitives.Longs;
import org.json.JSONException; import org.json.JSONException;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import eu.siacs.conversations.Config; import eu.siacs.conversations.Config;
import eu.siacs.conversations.crypto.axolotl.FingerprintStatus; import eu.siacs.conversations.crypto.axolotl.FingerprintStatus;
import eu.siacs.conversations.http.URL;
import eu.siacs.conversations.services.AvatarService; import eu.siacs.conversations.services.AvatarService;
import eu.siacs.conversations.ui.util.PresenceSelector; import eu.siacs.conversations.ui.util.PresenceSelector;
import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.CryptoHelper;
@ -113,7 +113,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
private Message mPreviousMessage = null; private Message mPreviousMessage = null;
private String axolotlFingerprint = null; private String axolotlFingerprint = null;
private String errorMessage = null; private String errorMessage = null;
private Set<ReadByMarker> readByMarkers = new HashSet<>(); private Set<ReadByMarker> readByMarkers = new CopyOnWriteArraySet<>();
private Boolean isGeoUri = null; private Boolean isGeoUri = null;
private Boolean isEmojisOnly = null; private Boolean isEmojisOnly = null;
@ -206,7 +206,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
this.edits = Edit.fromJson(edited); this.edits = Edit.fromJson(edited);
this.oob = oob; this.oob = oob;
this.errorMessage = errorMessage; this.errorMessage = errorMessage;
this.readByMarkers = readByMarkers == null ? new HashSet<>() : readByMarkers; this.readByMarkers = readByMarkers == null ? new CopyOnWriteArraySet<>() : readByMarkers;
this.markable = markable; this.markable = markable;
this.deleted = deleted; this.deleted = deleted;
this.bodyLanguage = bodyLanguage; this.bodyLanguage = bodyLanguage;
@ -547,7 +547,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
} else { } else {
String body, otherBody; String body, otherBody;
if (this.hasFileOnRemoteHost()) { if (this.hasFileOnRemoteHost()) {
body = getFileParams().url.toString(); body = getFileParams().url;
otherBody = message.body == null ? null : message.body.trim(); otherBody = message.body == null ? null : message.body.trim();
} else { } else {
body = this.body; body = this.body;
@ -684,6 +684,11 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
} }
} }
@Override
public String getAvatarName() {
return UIHelper.getMessageDisplayName(this);
}
public boolean isOOb() { public boolean isOOb() {
return oob; return oob;
} }
@ -789,12 +794,11 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
if (relativeFilePath != null) { if (relativeFilePath != null) {
extension = MimeUtils.extractRelevantExtension(relativeFilePath); extension = MimeUtils.extractRelevantExtension(relativeFilePath);
} else { } else {
try { final String url = URL.tryParse(body.split("\n")[0]);
final URL url = new URL(body.split("\n")[0]); if (url == null) {
extension = MimeUtils.extractRelevantExtension(url);
} catch (MalformedURLException e) {
return null; return null;
} }
extension = MimeUtils.extractRelevantExtension(url);
} }
return MimeUtils.guessMimeTypeFromExtension(extension); return MimeUtils.guessMimeTypeFromExtension(extension);
} }
@ -835,8 +839,8 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
case 1: case 1:
try { try {
fileParams.size = Long.parseLong(parts[0]); fileParams.size = Long.parseLong(parts[0]);
} catch (NumberFormatException e) { } catch (final NumberFormatException e) {
fileParams.url = parseUrl(parts[0]); fileParams.url = URL.tryParse(parts[0]);
} }
break; break;
case 5: case 5:
@ -845,11 +849,11 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
fileParams.width = parseInt(parts[2]); fileParams.width = parseInt(parts[2]);
fileParams.height = parseInt(parts[3]); fileParams.height = parseInt(parts[3]);
case 2: case 2:
fileParams.url = parseUrl(parts[0]); fileParams.url = URL.tryParse(parts[0]);
fileParams.size = parseLong(parts[1]); fileParams.size = Longs.tryParse(parts[1]);
break; break;
case 3: case 3:
fileParams.size = parseLong(parts[0]); fileParams.size = Longs.tryParse(parts[0]);
fileParams.width = parseInt(parts[1]); fileParams.width = parseInt(parts[1]);
fileParams.height = parseInt(parts[2]); fileParams.height = parseInt(parts[2]);
break; break;
@ -858,14 +862,6 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
return fileParams; return fileParams;
} }
private static long parseLong(String value) {
try {
return Long.parseLong(value);
} catch (NumberFormatException e) {
return 0;
}
}
private static int parseInt(String value) { private static int parseInt(String value) {
try { try {
return Integer.parseInt(value); return Integer.parseInt(value);
@ -874,14 +870,6 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
} }
} }
private static URL parseUrl(String value) {
try {
return new URL(value);
} catch (MalformedURLException e) {
return null;
}
}
public void untie() { public void untie() {
this.mNextMessage = null; this.mNextMessage = null;
this.mPreviousMessage = null; this.mPreviousMessage = null;
@ -895,6 +883,11 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
return type == TYPE_FILE || type == TYPE_IMAGE || type == TYPE_PRIVATE_FILE; return type == TYPE_FILE || type == TYPE_IMAGE || type == TYPE_PRIVATE_FILE;
} }
public boolean isTypeText() {
return type == TYPE_TEXT || type == TYPE_PRIVATE;
}
public boolean hasFileOnRemoteHost() { public boolean hasFileOnRemoteHost() {
return isFileOrImage() && getFileParams().url != null; return isFileOrImage() && getFileParams().url != null;
} }
@ -903,12 +896,16 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
return isFileOrImage() && getFileParams().url == null; return isFileOrImage() && getFileParams().url == null;
} }
public class FileParams { public static class FileParams {
public URL url; public String url;
public long size = 0; public Long size = null;
public int width = 0; public int width = 0;
public int height = 0; public int height = 0;
public int runtime = 0; public int runtime = 0;
public long getSize() {
return size == null ? 0 : size;
}
} }
public void setFingerprint(String fingerprint) { public void setFingerprint(String fingerprint) {
@ -987,13 +984,28 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
} }
if (conversation.getMode() == Conversation.MODE_MULTI) { if (conversation.getMode() == Conversation.MODE_MULTI) {
final Jid nextCounterpart = conversation.getNextCounterpart(); final Jid nextCounterpart = conversation.getNextCounterpart();
if (nextCounterpart != null) { return configurePrivateMessage(conversation, message, nextCounterpart, isFile);
message.setCounterpart(nextCounterpart); }
message.setTrueCounterpart(conversation.getMucOptions().getTrueCounterpart(nextCounterpart)); return false;
}
public static boolean configurePrivateMessage(final Message message, final Jid counterpart) {
final Conversation conversation;
if (message.conversation instanceof Conversation) {
conversation = (Conversation) message.conversation;
} else {
return false;
}
return configurePrivateMessage(conversation, message, counterpart, false);
}
private static boolean configurePrivateMessage(final Conversation conversation, final Message message, final Jid counterpart, final boolean isFile) {
if (counterpart == null) {
return false;
}
message.setCounterpart(counterpart);
message.setTrueCounterpart(conversation.getMucOptions().getTrueCounterpart(counterpart));
message.setType(isFile ? Message.TYPE_PRIVATE_FILE : Message.TYPE_PRIVATE); message.setType(isFile ? Message.TYPE_PRIVATE_FILE : Message.TYPE_PRIVATE);
return true; return true;
} }
} }
return false;
}
}

View File

@ -1,9 +1,10 @@
package eu.siacs.conversations.entities; package eu.siacs.conversations.entities;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils; import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
@ -17,11 +18,11 @@ import eu.siacs.conversations.services.AvatarService;
import eu.siacs.conversations.services.MessageArchiveService; import eu.siacs.conversations.services.MessageArchiveService;
import eu.siacs.conversations.utils.JidHelper; import eu.siacs.conversations.utils.JidHelper;
import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.utils.UIHelper;
import eu.siacs.conversations.xmpp.Jid;
import eu.siacs.conversations.xmpp.chatstate.ChatState; import eu.siacs.conversations.xmpp.chatstate.ChatState;
import eu.siacs.conversations.xmpp.forms.Data; import eu.siacs.conversations.xmpp.forms.Data;
import eu.siacs.conversations.xmpp.forms.Field; import eu.siacs.conversations.xmpp.forms.Field;
import eu.siacs.conversations.xmpp.pep.Avatar; import eu.siacs.conversations.xmpp.pep.Avatar;
import eu.siacs.conversations.xmpp.Jid;
public class MucOptions { public class MucOptions {
@ -37,7 +38,7 @@ public class MucOptions {
private final Conversation conversation; private final Conversation conversation;
public OnRenameListener onRenameListener = null; public OnRenameListener onRenameListener = null;
private boolean mAutoPushConfiguration = true; private boolean mAutoPushConfiguration = true;
private Account account; private final Account account;
private ServiceDiscoveryResult serviceDiscoveryResult; private ServiceDiscoveryResult serviceDiscoveryResult;
private boolean isOnline = false; private boolean isOnline = false;
private Error error = Error.NONE; private Error error = Error.NONE;
@ -211,6 +212,10 @@ public class MucOptions {
return conversation.getBooleanAttribute(Conversation.ATTRIBUTE_MODERATED, false); return conversation.getBooleanAttribute(Conversation.ATTRIBUTE_MODERATED, false);
} }
public boolean stableId() {
return getFeatures().contains("http://jabber.org/protocol/muc#stable_id");
}
public User deleteUser(Jid jid) { public User deleteUser(Jid jid) {
User user = findUserByFullJid(jid); User user = findUserByFullJid(jid);
if (user != null) { if (user != null) {
@ -629,8 +634,8 @@ public class MucOptions {
OUTCAST(0, R.string.outcast), OUTCAST(0, R.string.outcast),
NONE(1, R.string.no_affiliation); NONE(1, R.string.no_affiliation);
private int resId; private final int resId;
private int rank; private final int rank;
Affiliation(int rank, int resId) { Affiliation(int rank, int resId) {
this.resId = resId; this.resId = resId;
@ -672,8 +677,8 @@ public class MucOptions {
PARTICIPANT(R.string.participant, 2), PARTICIPANT(R.string.participant, 2),
NONE(R.string.no_role, 0); NONE(R.string.no_role, 0);
private int resId; private final int resId;
private int rank; private final int rank;
Role(int resId, int rank) { Role(int resId, int rank) {
this.resId = resId; this.resId = resId;
@ -740,7 +745,7 @@ public class MucOptions {
private Jid fullJid; private Jid fullJid;
private long pgpKeyId = 0; private long pgpKeyId = 0;
private Avatar avatar; private Avatar avatar;
private MucOptions options; private final MucOptions options;
private ChatState chatState = Config.DEFAULT_CHAT_STATE; private ChatState chatState = Config.DEFAULT_CHAT_STATE;
public User(MucOptions options, Jid fullJid) { public User(MucOptions options, Jid fullJid) {
@ -851,7 +856,7 @@ public class MucOptions {
@Override @Override
public String toString() { public String toString() {
return "[fulljid:" + String.valueOf(fullJid) + ",realjid:" + String.valueOf(realJid) + ",affiliation" + affiliation.toString() + "]"; return "[fulljid:" + fullJid + ",realjid:" + realJid + ",affiliation" + affiliation.toString() + "]";
} }
public boolean realJidMatchesAccount() { public boolean realJidMatchesAccount() {
@ -900,5 +905,10 @@ public class MucOptions {
final String seed = realJid != null ? realJid.asBareJid().toString() : null; final String seed = realJid != null ? realJid.asBareJid().toString() : null;
return UIHelper.getColorForName(seed == null ? getName() : seed); return UIHelper.getColorForName(seed == null ? getName() : seed);
} }
@Override
public String getAvatarName() {
return getConversation().getName().toString();
}
} }
} }

Some files were not shown because too many files have changed in this diff Show More