Compare commits

...
This repository has been archived on 2025-01-10. You can view files and clone it, but cannot push or open issues or pull requests.

563 commits

Author SHA1 Message Date
Christian Schneppe
598e619965
version 2.3.7 + CHANGELOG 2020-04-07 21:27:26 +02:00
Christian Schneppe
dddff7965f
fix updater 2020-04-07 21:26:49 +02:00
Christian Schneppe
a7fd6b31e2
fix resolving IP if SRV violences RFC2782 2020-04-07 20:54:54 +02:00
Christian Schneppe
fdfaae6f59
fix resolving IP if SRV violences RFC2782 2020-04-07 20:08:25 +02:00
Christian Schneppe
e2c2ee6507
version 2.3.6 + CHANGELOG 2020-04-06 22:30:43 +02:00
Christian Schneppe
3a5abac6d9
New Crowdin translations (#463)
* New translations strings.xml (Romanian)

* New translations strings.xml (Swedish)

* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (Portuguese)

* New translations strings.xml (Greek)

* New translations strings.xml (Czech)

* New translations strings.xml (Kannada)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Ukrainian)

* New translations strings.xml (Turkish)

* New translations strings.xml (Spanish)

* New translations strings.xml (Russian)

* New translations strings.xml (Polish)

* New translations strings.xml (Azerbaijani)

* New translations strings.xml (Italian)

* New translations strings.xml (Indonesian)

* New translations strings.xml (German)

* New translations strings.xml (Galician)

* New translations strings.xml (French)

* New translations strings.xml (Filipino)

* New translations strings.xml (Dutch)

* New translations strings.xml (Chinese Traditional)

* New translations strings.xml (Cebuano)

* New translations strings.xml (Catalan)

* New translations strings.xml (Bulgarian)

* New translations strings.xml (Basque)

* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (Portuguese)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Ukrainian)

* New translations strings.xml (Spanish)

* New translations strings.xml (Italian)

* New translations strings.xml (Chinese Traditional)

* New translations strings.xml (French)

* New translations strings.xml (French)

* New translations strings.xml (Azerbaijani)

* New translations strings.xml (Azerbaijani)

* New translations strings.xml (Russian)

* New translations strings.xml (Azerbaijani)

* New translations strings.xml (Azerbaijani)

* New translations strings.xml (Azerbaijani)

* New translations strings.xml (Azerbaijani)

* New translations strings.xml (Azerbaijani)

* New translations strings.xml (Azerbaijani)

* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (Portuguese)

* New translations strings.xml (Kannada)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Spanish)

* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (Portuguese)

* New translations strings.xml (French)

* New translations strings.xml (Chinese Traditional)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Azerbaijani)

* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (Portuguese)

* New translations strings.xml (Chinese Traditional)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Azerbaijani)

* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (Portuguese)

* New translations strings.xml (Chinese Traditional)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (Portuguese)

* New translations strings.xml (Russian)

* New translations strings.xml (Chinese Traditional)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (Portuguese)

* New translations strings.xml (Chinese Traditional)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Arabic)

* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (Portuguese)

* New translations strings.xml (Chinese Traditional)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Azerbaijani)

* New translations strings.xml (Spanish)

* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (Portuguese)

* New translations strings.xml (Russian)

* New translations strings.xml (French)

* New translations strings.xml (Chinese Traditional)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Ukrainian)

* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (Portuguese)

* New translations strings.xml (Italian)

* New translations strings.xml (Chinese Traditional)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (Portuguese)

* New translations strings.xml (German)

* New translations strings.xml (French)

* New translations strings.xml (Chinese Traditional)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (Portuguese)

* New translations strings.xml (German)

* New translations strings.xml (Chinese Traditional)

* New translations strings.xml (Chinese Simplified)
2020-04-06 22:26:53 +02:00
Christian Schneppe
4f48eb877f New translations strings.xml (Chinese Simplified) 2020-04-06 22:25:58 +02:00
Christian Schneppe
be8d156460 New translations strings.xml (Chinese Traditional) 2020-04-06 22:25:56 +02:00
Christian Schneppe
626a596b67 New translations strings.xml (German) 2020-04-06 22:25:42 +02:00
Christian Schneppe
c10c335f0d New translations strings.xml (Portuguese) 2020-04-06 22:25:34 +02:00
Christian Schneppe
879ffada02 New translations strings.xml (Portuguese, Brazilian) 2020-04-06 22:25:31 +02:00
Christian Schneppe
bd139f9b53
make OTR setting working 2020-04-06 22:16:16 +02:00
Christian Schneppe
e0415d1ac5
old android compatibility 2020-04-06 22:10:01 +02:00
Christian Schneppe
ae5947bc7e New translations strings.xml (Chinese Simplified) 2020-04-06 21:56:57 +02:00
Christian Schneppe
52329da9e5 New translations strings.xml (Chinese Traditional) 2020-04-06 21:56:54 +02:00
Christian Schneppe
68ba8e37bc New translations strings.xml (French) 2020-04-06 21:56:45 +02:00
Christian Schneppe
0071e5bd37 New translations strings.xml (German) 2020-04-06 21:56:39 +02:00
Christian Schneppe
bac1ef1160 New translations strings.xml (Portuguese) 2020-04-06 21:56:32 +02:00
Christian Schneppe
af1c1b97b7 New translations strings.xml (Portuguese, Brazilian) 2020-04-06 21:56:30 +02:00
Christian Schneppe
089ca1c414
fix crach on android < SDK 21 2020-04-06 21:22:03 +02:00
Christian Schneppe
8fcaea2e17 New translations strings.xml (Chinese Simplified) 2020-04-05 21:32:50 +02:00
Christian Schneppe
e6ae2427ea New translations strings.xml (Chinese Traditional) 2020-04-05 21:32:47 +02:00
Christian Schneppe
d88ea3b078 New translations strings.xml (Italian) 2020-04-05 21:32:33 +02:00
Christian Schneppe
fa36af1695 New translations strings.xml (Portuguese) 2020-04-05 21:32:27 +02:00
Christian Schneppe
e05f1a802b New translations strings.xml (Portuguese, Brazilian) 2020-04-05 21:32:24 +02:00
Christian Schneppe
7d03a39302 New translations strings.xml (Ukrainian) 2020-04-02 21:36:22 +02:00
Christian Schneppe
fa083983a8 New translations strings.xml (Chinese Simplified) 2020-04-02 21:36:10 +02:00
Christian Schneppe
bdf31aea33 New translations strings.xml (Chinese Traditional) 2020-04-02 21:36:09 +02:00
Christian Schneppe
f7dc10afe6 New translations strings.xml (French) 2020-04-02 21:36:01 +02:00
Christian Schneppe
0b627e818e New translations strings.xml (Russian) 2020-04-02 21:35:57 +02:00
Christian Schneppe
f114694eb3 New translations strings.xml (Portuguese) 2020-04-02 21:35:46 +02:00
Christian Schneppe
8e8ed92308 New translations strings.xml (Portuguese, Brazilian) 2020-04-02 21:35:43 +02:00
Christian Schneppe
6850073ab3 New translations strings.xml (Spanish) 2020-04-02 21:35:37 +02:00
Christian Schneppe
e80136862a New translations strings.xml (Azerbaijani) 2020-04-02 21:35:27 +02:00
Christian Schneppe
5d99bff131
version 2.3.5 2020-04-02 20:54:15 +02:00
Christian Schneppe
6f506d4021
respect invidious settings in notifications 2020-04-02 20:49:58 +02:00
Christian Schneppe
e778db81c7 New translations strings.xml (Chinese Simplified) 2020-04-01 21:37:31 +02:00
Christian Schneppe
7208a71045 New translations strings.xml (Chinese Traditional) 2020-04-01 21:37:28 +02:00
Christian Schneppe
340510db9c New translations strings.xml (Portuguese) 2020-04-01 21:37:07 +02:00
Christian Schneppe
439bd354a1 New translations strings.xml (Portuguese, Brazilian) 2020-04-01 21:37:03 +02:00
Christian Schneppe
f816abb047
display toast when trying to join channel with no enabled accounts 2020-04-01 21:35:36 +02:00
Christian Schneppe
fb56abb00f
fixed typo 2020-04-01 21:32:10 +02:00
Christian Schneppe
ac2f33aa82
close FileInputStream in MTM 2020-04-01 21:30:39 +02:00
Christian Schneppe
3732f8c82b New translations strings.xml (Arabic) 2020-03-30 12:21:16 +02:00
Christian Schneppe
829f4122b3 New translations strings.xml (Chinese Simplified) 2020-03-30 12:21:06 +02:00
Christian Schneppe
8b6fbc43bf New translations strings.xml (Chinese Traditional) 2020-03-30 12:21:02 +02:00
Christian Schneppe
5ac2ea4ce0 New translations strings.xml (Portuguese) 2020-03-30 12:20:41 +02:00
Christian Schneppe
31321c0ef9 New translations strings.xml (Portuguese, Brazilian) 2020-03-30 12:20:38 +02:00
Christian Schneppe
6d62eca223 New translations strings.xml (Chinese Simplified) 2020-03-29 12:16:46 +02:00
Christian Schneppe
ef57241e15 New translations strings.xml (Chinese Traditional) 2020-03-29 12:16:44 +02:00
Christian Schneppe
f083e06215 New translations strings.xml (Russian) 2020-03-29 12:16:34 +02:00
Christian Schneppe
91123d1229 New translations strings.xml (Portuguese) 2020-03-29 12:16:24 +02:00
Christian Schneppe
34ed48900a New translations strings.xml (Portuguese, Brazilian) 2020-03-29 12:16:21 +02:00
Christian Schneppe
9c962a4591 New translations strings.xml (Chinese Simplified) 2020-03-27 15:15:51 +01:00
Christian Schneppe
6683d4b27c New translations strings.xml (Chinese Traditional) 2020-03-27 15:15:48 +01:00
Christian Schneppe
5b305f0548 New translations strings.xml (Portuguese) 2020-03-27 15:15:23 +01:00
Christian Schneppe
b9dea9c4f3 New translations strings.xml (Portuguese, Brazilian) 2020-03-27 15:15:21 +01:00
Christian Schneppe
03ecff4c18 New translations strings.xml (Azerbaijani) 2020-03-27 15:15:07 +01:00
Christian Schneppe
7507a959b3 New translations strings.xml (Chinese Simplified) 2020-03-26 14:52:43 +01:00
Christian Schneppe
9ced052e2b New translations strings.xml (Chinese Traditional) 2020-03-26 14:52:40 +01:00
Christian Schneppe
f495a9f762 New translations strings.xml (Portuguese) 2020-03-26 14:52:20 +01:00
Christian Schneppe
38fb3e96b0 New translations strings.xml (Portuguese, Brazilian) 2020-03-26 14:52:18 +01:00
Christian Schneppe
efafe29c08 New translations strings.xml (Azerbaijani) 2020-03-26 14:52:02 +01:00
Christian Schneppe
ca486709ad New translations strings.xml (Chinese Simplified) 2020-03-23 18:27:40 +01:00
Christian Schneppe
8b9749eeec New translations strings.xml (Chinese Traditional) 2020-03-23 18:27:37 +01:00
Christian Schneppe
c019e99e10 New translations strings.xml (French) 2020-03-23 18:27:29 +01:00
Christian Schneppe
4dde9eb81f New translations strings.xml (Portuguese) 2020-03-23 18:27:14 +01:00
Christian Schneppe
ceae89869c New translations strings.xml (Portuguese, Brazilian) 2020-03-23 18:27:11 +01:00
Christian Schneppe
263db81722 New translations strings.xml (Spanish) 2020-03-23 18:27:05 +01:00
Christian Schneppe
db603c330b New translations strings.xml (Chinese Simplified) 2020-03-22 18:31:51 +01:00
Christian Schneppe
fa6ead101c New translations strings.xml (Kannada) 2020-03-22 18:31:34 +01:00
Christian Schneppe
a34a28ef1c New translations strings.xml (Portuguese) 2020-03-22 18:31:29 +01:00
Christian Schneppe
249c0ac7cd New translations strings.xml (Portuguese, Brazilian) 2020-03-22 18:31:26 +01:00
Christian Schneppe
30e4423934
Merge branch 'master' of https://github.com/kriztan/Pix-Art-Messenger 2020-03-22 18:25:31 +01:00
Christian Schneppe
f9b453d879
add choose video from file to attachments menu 2020-03-22 18:24:50 +01:00
Christian Schneppe
e431d3d092
fix MediaBrowserActivity and show if no attachments available 2020-03-22 18:24:08 +01:00
Christian Schneppe
49458bb09f
update dependencies 2020-03-22 18:06:59 +01:00
Christian Schneppe
a85f0b4d9e New translations strings.xml (Azerbaijani) 2020-03-20 07:59:39 +01:00
Echolon
493055f2cb
Update encryption.md 2020-03-19 21:33:17 +01:00
Echolon
7266eba56d
Update encryption.md
Added to references to projects which still support OTR.
2020-03-19 21:28:17 +01:00
Christian Schneppe
86c90ebcf7 New translations strings.xml (Azerbaijani) 2020-03-19 05:22:03 +01:00
Christian Schneppe
12706e565a New translations strings.xml (Azerbaijani) 2020-03-18 05:11:41 +01:00
Christian Schneppe
a6f99f4213 New translations strings.xml (Azerbaijani) 2020-03-17 05:14:06 +01:00
Christian Schneppe
7fa19f4f8c
update Conversation and log filename on restoring orphaned files 2020-03-16 21:30:00 +01:00
Christian Schneppe
885f22f806
version 2.3.5 beta (2020-03-16) + changes 2020-03-16 21:11:55 +01:00
Christian Schneppe
4aebdc5fa0
fix message quotes from SearchActivity 2020-03-16 20:39:12 +01:00
Christian Schneppe
ebecb6de7a
add jfif and jif as jpeg mime types 2020-03-16 19:55:58 +01:00
Christian Schneppe
b2cc172423
explicitly use BouncyCastle for file crypto 2020-03-16 19:54:33 +01:00
Christian Schneppe
e64a7c7a45
avoid bundled source selection that comes with ImageCropper on Android 10 2020-03-16 19:45:37 +01:00
Christian Schneppe
d729a11d26
when setting moderated also set non standard field to not make users participants by default 2020-03-16 19:41:16 +01:00
Christian Schneppe
01f10a7329
set IqPacket publish in AxolotlService final 2020-03-16 19:39:36 +01:00
Christian Schneppe
4c6599b395
do not merge oob messages 2020-03-16 19:38:31 +01:00
Christian Schneppe
755625eec5
show invidious links in notifications too 2020-03-16 19:32:02 +01:00
Christian Schneppe
dfdf8014d8
fix NPE 2020-03-16 19:12:10 +01:00
Christian Schneppe
063ee0bd72 New translations strings.xml (Azerbaijani) 2020-03-16 04:04:40 +01:00
Christian Schneppe
a6a795f605 New translations strings.xml (Azerbaijani) 2020-03-15 04:02:13 +01:00
Christian Schneppe
3931ec3601 New translations strings.xml (Russian) 2020-03-10 21:27:35 +01:00
Christian Schneppe
7bc7a864ed New translations strings.xml (Azerbaijani) 2020-03-10 21:27:31 +01:00
Christian Schneppe
ca066cef90 New translations strings.xml (Azerbaijani) 2020-03-09 21:24:49 +01:00
Christian Schneppe
9816664d02 New translations strings.xml (French) 2020-02-25 21:11:46 +01:00
Christian Schneppe
e058ee868f New translations strings.xml (French) 2020-02-24 21:11:44 +01:00
Christian Schneppe
260d23bdbc New translations strings.xml (Chinese Traditional) 2020-02-19 21:42:50 +01:00
Christian Schneppe
c5460973aa New translations strings.xml (Italian) 2020-02-19 21:42:37 +01:00
Christian Schneppe
e381d4613e New translations strings.xml (Spanish) 2020-02-19 21:42:27 +01:00
Christian Schneppe
e8e26ab944 New translations strings.xml (Ukrainian) 2020-02-19 21:42:21 +01:00
Christian Schneppe
83ac39ff73 New translations strings.xml (Chinese Simplified) 2020-02-19 21:42:17 +01:00
Christian Schneppe
97cbe0f09d New translations strings.xml (Portuguese) 2020-02-19 21:42:07 +01:00
Christian Schneppe
f6a12159ef New translations strings.xml (Portuguese, Brazilian) 2020-02-19 21:42:04 +01:00
Christian Schneppe
33d082d446
version 2.3.5 beta (2020-02-19) 2020-02-19 20:41:39 +01:00
Christian Schneppe
4ce7c171ec
don't show offline status in dynamic tags 2020-02-19 20:40:49 +01:00
genofire
dec454ab82
WIP: happy eyeball with dns caching for 5min (#464)
* happy eyeball: fix dnssec for plain ip an srv-cname

* reimplement dns resolver cache + add timeout for cache
2020-02-19 20:32:04 +01:00
Christian Schneppe
3ff205a724
Revert "hide local part of group chat xmpp address"
This reverts commit 31abe938fd.
2020-02-19 19:28:57 +01:00
Christian Schneppe
9288adbec7 New translations strings.xml (Basque) 2020-02-18 21:42:31 +01:00
Christian Schneppe
74cdaace14 New translations strings.xml (Bulgarian) 2020-02-18 21:42:28 +01:00
Christian Schneppe
737ed457a3 New translations strings.xml (Catalan) 2020-02-18 21:42:26 +01:00
Christian Schneppe
30b5f1cc5a New translations strings.xml (Cebuano) 2020-02-18 21:42:22 +01:00
Christian Schneppe
defb4c7044 New translations strings.xml (Chinese Traditional) 2020-02-18 21:42:20 +01:00
Christian Schneppe
a221959b4a New translations strings.xml (Dutch) 2020-02-18 21:42:19 +01:00
Christian Schneppe
4b34f2351e New translations strings.xml (Filipino) 2020-02-18 21:42:15 +01:00
Christian Schneppe
b8acf9469d New translations strings.xml (French) 2020-02-18 21:42:13 +01:00
Christian Schneppe
6b6483341d New translations strings.xml (Galician) 2020-02-18 21:42:09 +01:00
Christian Schneppe
55ed4223dd New translations strings.xml (German) 2020-02-18 21:42:06 +01:00
Christian Schneppe
1e86c249f7 New translations strings.xml (Indonesian) 2020-02-18 21:42:04 +01:00
Christian Schneppe
efdc848dfb New translations strings.xml (Italian) 2020-02-18 21:42:01 +01:00
Christian Schneppe
bb472eb1bf New translations strings.xml (Azerbaijani) 2020-02-18 21:41:59 +01:00
Christian Schneppe
b7e2009404 New translations strings.xml (Polish) 2020-02-18 21:41:56 +01:00
Christian Schneppe
d5bbf8d232 New translations strings.xml (Russian) 2020-02-18 21:41:53 +01:00
Christian Schneppe
d9cf186293 New translations strings.xml (Spanish) 2020-02-18 21:41:52 +01:00
Christian Schneppe
30b3638941 New translations strings.xml (Turkish) 2020-02-18 21:41:49 +01:00
Christian Schneppe
95d834e8eb New translations strings.xml (Ukrainian) 2020-02-18 21:41:46 +01:00
Christian Schneppe
4f2d20dbd5 New translations strings.xml (Chinese Simplified) 2020-02-18 21:41:44 +01:00
Christian Schneppe
8bcc75c6c8 New translations strings.xml (Kannada) 2020-02-18 21:41:41 +01:00
Christian Schneppe
c5d6c9b026 New translations strings.xml (Czech) 2020-02-18 21:41:39 +01:00
Christian Schneppe
076e32555d New translations strings.xml (Greek) 2020-02-18 21:41:36 +01:00
Christian Schneppe
ce38821089 New translations strings.xml (Portuguese) 2020-02-18 21:41:34 +01:00
Christian Schneppe
1267228788 New translations strings.xml (Portuguese, Brazilian) 2020-02-18 21:41:31 +01:00
Christian Schneppe
0e8e5f8df2 New translations strings.xml (Swedish) 2020-02-18 21:41:29 +01:00
Christian Schneppe
1779e1262c New translations strings.xml (Romanian) 2020-02-18 21:41:26 +01:00
Christian Schneppe
55acdcbbb8
fix MessageAdapter 2020-02-18 21:19:51 +01:00
Christian Schneppe
6dff9bc8a9
add context menu to MediaBrowser to
* share
* open
* delete
2020-02-18 21:19:22 +01:00
Christian Schneppe
ad9f8fde90
update chat background immediately after opening chat 2020-02-18 19:44:49 +01:00
Christian Schneppe
d941ec1926
version 2.3.5 beta (2020-02-17) + CHANGELOG 2020-02-17 21:44:19 +01:00
Christian Schneppe
43ef8edc7c
Merge branch 'master' of https://github.com/kriztan/Pix-Art-Messenger 2020-02-17 21:41:06 +01:00
Christian Schneppe
d189dbf03b
New Crowdin translations (#452)
* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (Portuguese)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Spanish)

* New translations strings.xml (Chinese Traditional)

* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (Portuguese)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Ukrainian)

* New translations strings.xml (Spanish)

* New translations strings.xml (Russian)

* New translations strings.xml (Polish)

* New translations strings.xml (Italian)

* New translations strings.xml (German)

* New translations strings.xml (Chinese Traditional)

* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (Portuguese)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Spanish)

* New translations strings.xml (Chinese Traditional)

* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (Portuguese)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Ukrainian)

* New translations strings.xml (Chinese Traditional)

* New translations strings.xml (German)

* New translations strings.xml (Spanish)

* New translations strings.xml (Ukrainian)

* New translations strings.xml (Kannada)

* New translations strings.xml (Arabic)

* New translations strings.xml (Romanian)

* New translations strings.xml (Swedish)

* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (Portuguese)

* New translations strings.xml (Greek)

* New translations strings.xml (Czech)

* New translations strings.xml (Kannada)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Ukrainian)

* New translations strings.xml (Turkish)

* New translations strings.xml (Spanish)

* New translations strings.xml (Russian)

* New translations strings.xml (Polish)

* New translations strings.xml (Azerbaijani)

* New translations strings.xml (Italian)

* New translations strings.xml (Indonesian)

* New translations strings.xml (German)

* New translations strings.xml (Galician)

* New translations strings.xml (French)

* New translations strings.xml (Filipino)

* New translations strings.xml (Dutch)

* New translations strings.xml (Chinese Traditional)

* New translations strings.xml (Cebuano)

* New translations strings.xml (Catalan)

* New translations strings.xml (Bulgarian)

* New translations strings.xml (Basque)

* New translations strings.xml (Tagalog)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Chinese Traditional)

* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (Portuguese)

* New translations strings.xml (Italian)

* New translations strings.xml (Russian)

* New translations strings.xml (Spanish)

* New translations strings.xml (German)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Chinese Traditional)

* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (Portuguese)

* New translations strings.xml (Italian)
2020-02-17 21:40:32 +01:00
Christian Schneppe
649a261706
fix some more colors for orange theme 2020-02-17 21:26:43 +01:00
Christian Schneppe
bb4e06c950
fix backgroundcolor for selected items in contact chooser 2020-02-17 21:00:19 +01:00
Christian Schneppe
d606577b4d
switch to sending 12 byte IVs 2020-02-17 20:10:52 +01:00
Christian Schneppe
31abe938fd
hide local part of group chat xmpp address 2020-02-17 20:10:17 +01:00
Christian Schneppe
5014248ccb
prevent sharing of xmpp uri for group chat bookmarks 2020-02-17 20:08:44 +01:00
Christian Schneppe
2420f3f2cd
do not warn user if bookmark already exists 2020-02-17 20:07:42 +01:00
Christian Schneppe
c2c8bef66a
small fixes 2020-02-17 20:04:55 +01:00
Christian Schneppe
f3e9134002
customize ExoPlayer 2020-02-17 20:03:12 +01:00
Christian Schneppe
435989fc60
use androidx.appcompat.app.AlertDialog and make AlertDialog final 2020-02-16 22:10:06 +01:00
Christian Schneppe
15d25f3658
fix user adapter view recycling bug 2020-02-16 11:40:29 +01:00
Christian Schneppe
fd2bfdb459
correct AbstractGenerator 2020-02-16 11:28:00 +01:00
Christian Schneppe
73eaea0e5f
some changes to XmppConnectionService 2020-02-16 11:23:31 +01:00
Christian Schneppe
d5f1690724
add ability to delete own files from MediaViewerActivity 2020-02-16 10:48:14 +01:00
Christian Schneppe
827ae43d8b
Merge branch 'master' of https://github.com/kriztan/Pix-Art-Messenger 2020-02-14 21:34:34 +01:00
Christian Schneppe
3f12418b60
add 'show avatar' to context menu in chat view
to show a bigger avatar
2020-02-14 19:52:48 +01:00
genofire
dd93aaf507
happy eyeball: fix NPE on connecting without dns (#460) 2020-02-13 16:49:02 +01:00
Christian Schneppe
3abe818259
Merge pull request #459 from licaon-kter/patch-9
Use German badges
2020-02-13 11:23:41 +01:00
Licaon_Kter
ed073c9d3b
Use German badges 2020-02-13 10:22:32 +00:00
Christian Schneppe
21ddcc478f
set filter to MediaBrowserActivity
to show only images and videos or all media files
fixes #457
2020-02-12 20:37:32 +01:00
Christian Schneppe
92b16348f4
show PDF preview in MediaBrowserActivity 2020-02-12 19:53:45 +01:00
Christian Schneppe
e4d3b5ebe8
version 2.3.5 beta (2020-02-11) 2020-02-11 21:09:16 +01:00
Christian Schneppe
17f4f4e492
fix NPE in resolver
and set more variables to final
2020-02-11 20:24:11 +01:00
Christian Schneppe
898218e740
make colored muc names configurable
* default = off
2020-02-11 20:16:03 +01:00
Christian Schneppe
71780abb89
rework PDF preview
should fix #456
2020-02-11 19:52:25 +01:00
Christian Schneppe
993fb18d52
fix colored nickname failure on single chats the 2nd 2020-02-10 21:25:54 +01:00
Christian Schneppe
088360b2d6
fix colored nickname failure on single chats 2020-02-10 20:50:49 +01:00
Christian Schneppe
52e874127b
version 2.3.5 (2020-02-10) + CHANGELOG 2020-02-10 20:22:41 +01:00
Christian Schneppe
fc777c3a80
catch more exceptions in JingleSocks5Transport 2020-02-10 20:21:40 +01:00
Christian Schneppe
e2d89da5d3
use colored usernames in group chats/channels 2020-02-10 20:10:04 +01:00
Christian Schneppe
caaaa05e81 Merge pull request #455 from subpub/patch-1
shorten has Written to Wrote
2020-02-10 09:54:56 +01:00
subpub
d03a9819e3 shorten has Written to Wrote 2020-02-10 08:23:23 +00:00
Christian Schneppe
d7a7db1ced Merge pull request #453 from genofire/resolver-fix
improve happy eyeball
2020-02-10 07:34:29 +01:00
genofire
9e24597dd2
Revert "Networkstack: drop own implementation of DNS-Server selection"
This reverts commit 3bbdd69d620055a88f42bc62e10421e250a57d9e.
2020-02-10 02:38:40 +01:00
genofire
ea063e8a76
[BUGFIX] Resolver: fallback for invalid SRV CNAME entries 2020-02-10 02:38:37 +01:00
genofire
470cc533b2
[BUGFIX] Resolver: allow srv entry with priority 0 2020-02-09 23:12:03 +01:00
genofire
6e9ea36d92
improve logging of happy eyeball (v2) 2020-02-09 23:10:17 +01:00
Christian Schneppe
db952297c5
integrate alternative codec for voice recorder via attachment settings 2020-02-09 21:39:57 +01:00
Christian Schneppe
b10b73caad
introduce preview for PDF files in chat 2020-02-09 21:03:37 +01:00
Christian Schneppe
482cb89267
version 2.3.5 beta (2020-02-09) 2020-02-09 15:34:18 +01:00
Christian Schneppe
896d73858d
exclude org.jetbrain annotations globally 2020-02-09 15:33:10 +01:00
Christian Schneppe
83c1ba7b1c Merge pull request #450 from genofire/resolver-fix
Happy Eyeball bug in concurrents connect failed
2020-02-09 13:47:46 +01:00
genofire
fc0daaf81d
improve logging of happy eyeball 2020-02-09 13:12:03 +01:00
genofire
afcfd057d8
[BUGFIX] happy eyeball: concurrents resolve connect 2020-02-09 13:11:40 +01:00
Christian Schneppe
c3da4be4da New Crowdin translations (#449)
* New translations strings.xml (Romanian)

* New translations strings.xml (Portuguese)

* New translations strings.xml (Kannada)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Ukrainian)

* New translations strings.xml (Spanish)

* New translations strings.xml (Russian)

* New translations strings.xml (Polish)

* New translations strings.xml (Azerbaijani)

* New translations strings.xml (Italian)

* New translations strings.xml (German)

* New translations strings.xml (Chinese Traditional)

* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (Portuguese)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (German)

* New translations strings.xml (Chinese Traditional)
2020-02-08 22:08:13 +01:00
Christian Schneppe
d2e1e316fc
add more logging and fix typo in Resolver.java 2020-02-08 21:35:42 +01:00
Christian Schneppe
56a0760fbb
version 2.3.5 beta (2020-02-02) + CHANGELOG 2020-02-08 19:31:12 +01:00
Christian Schneppe
ae09585ce2
add preferece to use unicolored background 2020-02-08 19:30:21 +01:00
Christian Schneppe
e96467e5ad
catch some more NPE in Resolver.java 2020-02-08 18:38:19 +01:00
Christian Schneppe
2d5a7e013a
Merge pull request #448 from genofire/resolver-fix
[BUGFIX] happy eye: null pointer if no connection was etablished
2020-02-08 16:33:56 +01:00
genofire
e125944cbc
[BUGFIX] happy eye: null pointer if no connection was etablished 2020-02-08 16:21:53 +01:00
Christian Schneppe
f0a05e133c
versdion 2.3.5 beta (2020-02-08) 2020-02-08 10:01:05 +01:00
Christian Schneppe
ac250ad8c0
make dark blue background a bit darker 2020-02-08 10:00:41 +01:00
Christian Schneppe
a61590ec33
fix scaling background 2020-02-08 09:55:16 +01:00
Christian Schneppe
bf420c711a
Merge pull request #446 from genofire/resolver-fix
[BUGFIX] happy eye: null pointer on closed socket if no connection was etablished
2020-02-08 09:00:48 +01:00
genofire
8efe986962
[BUGFIX] happy eye: null pointer on closed socket if no connection was etablished 2020-02-08 08:54:04 +01:00
Christian Schneppe
4fac8c47e7
version 2.3.5 beta (2020-02-07) 2020-02-07 21:15:42 +01:00
Christian Schneppe
b9d4699d9b
rework RecordingActivity 2020-02-07 21:11:02 +01:00
Christian Schneppe
ac0c766794
update jQuery 2020-02-07 20:16:39 +01:00
Christian Schneppe
c91f28ea31
update chat background 2020-02-07 20:15:56 +01:00
genofire
8dd82436bb
[BUGFIX] crash on resolve.toString if hostname is null (#444)
Thank you @genofire
2020-02-07 08:42:00 +01:00
Christian Schneppe
baa5ba17cd
fix some button colors 2020-02-06 21:19:21 +01:00
Christian Schneppe
3aac32f7aa
show me name instead of name part from own jid in maps 2020-02-06 20:11:42 +01:00
Christian Schneppe
9a9fc4ed5e
fix crash/stacktrace in Share/ShowLocationActivity 2020-02-06 19:57:52 +01:00
Christian Schneppe
2059bdff81
fix NPE in AboutActivity 2020-02-06 19:19:19 +01:00
Christian Schneppe
4611713eca
version 2.3.5 beta (2020-02-05) 2020-02-05 20:37:21 +01:00
genofire
22dc081ed1
Networkstack: easy happy eyeball (#411)
* Networkstack: easy happy eyeball

* Networkstack: drop own implementation of DNS-Server selection

Co-authored-by: Christian Schneppe <kriztan@users.noreply.github.com>
2020-02-05 20:25:36 +01:00
Christian Schneppe
1e5ebaa19c
limit artist - title in messages to 128 chars 2020-02-05 20:20:47 +01:00
Christian Schneppe
0da5d84cf4
add library excludes 2020-02-05 20:12:44 +01:00
Christian Schneppe
3741aee815
add device to crash report 2020-02-05 20:12:20 +01:00
Christian Schneppe
3c3c645c68
update MetaDataExtractor 2020-02-03 21:46:26 +01:00
Christian Schneppe
29bc3e9c7b
version 2.3.5 beta (2020-02-02) 2020-02-02 21:58:08 +01:00
Christian Schneppe
17ab39fe45
don't crash if player == null 2020-02-02 21:33:31 +01:00
Christian Schneppe
64826a6fd9
show audio artist and title for audio files 2020-02-02 21:32:56 +01:00
Christian Schneppe
68d7b433d8
correct build scripts 2020-02-01 20:36:52 +01:00
Christian Schneppe
a669e200b5
fix showing progressBar 2020-02-01 20:32:57 +01:00
Christian Schneppe
654b4c12cb
version 2.3.5 beta (2020-02-01) 2020-02-01 14:39:46 +01:00
Christian Schneppe
880e8b6eb7
version 2.3.5 beta (2020-02-01) 2020-02-01 14:37:13 +01:00
Christian Schneppe
9578b015ae
update libs 2020-02-01 14:34:31 +01:00
Christian Schneppe
40428e101e
remove unused resources 2020-02-01 14:11:35 +01:00
Christian Schneppe
217e3eee9b
update theme in SettingsActivity correctly 2020-02-01 14:09:41 +01:00
Christian Schneppe
857539eac3
show progressbar instead of percentages while up-/downloading files 2020-02-01 14:04:10 +01:00
Christian Schneppe
94d125945c
add ability to reply on private messages in notifications
fixes #191
2020-02-01 10:10:00 +01:00
Christian Schneppe
1373c583e9
small jingle fixes
possibly fixes #417, but I couldn't check this, because I can't reproduce the problems. Feel free to reopen it with updated logs.
2020-01-31 21:04:24 +01:00
Christian Schneppe
ec7cc243ad
release video player onStop 2020-01-31 20:51:41 +01:00
Christian Schneppe
12c190c85d
fix showing presence colored names
fixes #257
2020-01-31 20:51:04 +01:00
Christian Schneppe
d01e3bb203
version 2.3.5 beta (2020-01-31) + changes 2020-01-31 15:15:12 +01:00
Christian Schneppe
b3ee23d8f0
change updater a bit 2020-01-31 15:14:57 +01:00
Christian Schneppe
18be460e10
Merge branch 'master' of https://github.com/kriztan/Pix-Art-Messenger 2020-01-31 15:02:50 +01:00
Christian Schneppe
851f7b073d
use ToastCompat instead of Toast 2020-01-31 15:02:33 +01:00
Christian Schneppe
b0d89ba6ad
add icons for video, audio and image files for the case the preview fails 2020-01-31 14:34:10 +01:00
Christian Schneppe
82e4031e4d
optimize imports 2020-01-31 14:33:06 +01:00
Christian Schneppe
2f9d181822
remove FullscreenVideoViewer 2020-01-31 14:31:52 +01:00
Christian Schneppe
b0d0f904a2
fix sending uncompressed files 2020-01-31 14:29:55 +01:00
Christian Schneppe
83c976f2d7
use ExoPlayer for video playback 2020-01-31 14:28:27 +01:00
Christian Schneppe
6bb9db3686
evict cached previews when file gets deleted 2020-01-31 07:09:03 +01:00
Christian Schneppe
f5721e6c96
temporarily go back to 16 byte iv for imminent bug fix release 2020-01-31 07:04:11 +01:00
Christian Schneppe
0a2d4589fd
pass omemo decrypt up to higher layers to count as download error. decrypt all encrypted files
* added failure logging to http download
* fixed download of previously deleted omemo files
2020-01-31 07:03:26 +01:00
Christian Schneppe
efb3183eed
Update strings.xml 2020-01-27 14:18:11 +01:00
Christian Schneppe
cc5e65879d
correct appid which was accidently commited 2020-01-26 20:19:57 +01:00
Christian Schneppe
2f27378f16
use more material style 2020-01-26 20:18:13 +01:00
Christian Schneppe
0b857a81db
fix some bugs 2020-01-26 16:04:16 +01:00
Christian Schneppe
954cf63162
use default values for some preferences instead of hardcoded ones 2020-01-26 15:36:47 +01:00
Christian Schneppe
42c2dd48f0
rework lastseen a bit and don't show offline 2020-01-26 15:35:29 +01:00
Christian Schneppe
ac88ed360b
fix fingerprint for otr 2020-01-24 20:53:47 +01:00
Christian Schneppe
b9962f7b50
omemo changes: use 12 byte IV, no longer accept auth tag appended to payload 2020-01-24 20:36:29 +01:00
Christian Schneppe
a917c5403f
support xmpp uris with single 'omemo' parameter for fingerprint (w/o sid) 2020-01-24 20:29:53 +01:00
Christian Schneppe
6ff2ae4e02
fixed adding omemo encrypted images to gallery 2020-01-24 20:20:37 +01:00
Christian Schneppe
67f244555c
add scan qr button to welcome screen 2020-01-24 20:19:40 +01:00
Christian Schneppe
a6dd8e45fa
use installreferrer libray instead of broadcast listener 2020-01-24 19:55:58 +01:00
Christian Schneppe
04595db099
parse install referrer from gplay 2020-01-24 19:43:49 +01:00
Christian Schneppe
504e4f21a2
make registration uris work with fixed usernames 2020-01-24 19:28:14 +01:00
Christian Schneppe
99e21438c0
preselect proper account in create contact dialog after following invite 2020-01-24 17:21:33 +01:00
Christian Schneppe
e71bfaa48d
support ?roster;ibr=y xmpp uris 2020-01-24 17:13:40 +01:00
Christian Schneppe
6d74698aec
support registration via pars tokens 2020-01-24 17:08:30 +01:00
Christian Schneppe
f4a207e859
refactored xmpp uri parsing to expose all params 2020-01-24 16:10:56 +01:00
Christian Schneppe
a508f95c3f
show number of participants in a MUC 2020-01-24 16:02:46 +01:00
Christian Schneppe
c172f1ade7
try to heal Huawei's energy saving methods 2020-01-24 16:00:15 +01:00
Christian Schneppe
cc78954daa
Merge branch 'master' of https://github.com/kriztan/Pix-Art-Messenger 2020-01-14 18:54:20 +01:00
Christian Schneppe
9e752e637f
show toast message on PN if user is not in room/channel 2020-01-14 18:54:06 +01:00
Christian Schneppe
5bf46f8e85
show avatar correctly 2020-01-14 18:53:13 +01:00
Christian Schneppe
93e97be6db
switch volume control between earpiece and speaker while listening audio 2020-01-14 18:52:34 +01:00
Christian Schneppe
7d8f3720e8
Merge pull request #426 from licaon-kter/patch-8
Fix typo
2020-01-03 23:28:47 +01:00
Licaon_Kter
09a7aa6301
Fix typo
...be more specific
2020-01-03 22:14:48 +00:00
Christian Schneppe
e03da284fe
Merge pull request #425 from licaon-kter/patch-7
Fix typos
2020-01-03 19:40:53 +01:00
Licaon_Kter
bb6c72706d
Fix typos 2020-01-03 17:33:07 +00:00
Christian Schneppe
648fe344ae
Merge branch 'master' of https://github.com/kriztan/Pix-Art-Messenger 2020-01-02 15:14:59 +01:00
Christian Schneppe
ccdb122afb
rework map view and update leaflet to 1.6.0 2020-01-02 15:14:19 +01:00
Christian Schneppe
8cbc7b2a07
don’t crash when long pressing invalid geo-uris 2020-01-02 11:52:14 +01:00
Christian Schneppe
dd5f790b21
reset XmppConnection to parent 2020-01-02 11:50:43 +01:00
Christian Schneppe
d80c5fd971
don't create conversation while watching ContactDetails 2020-01-02 11:43:53 +01:00
Christian Schneppe
93519d7309
startup performance improvements 2020-01-02 11:43:01 +01:00
Christian Schneppe
5aa52b0824
reset file to normal message when attempting re-download after delete 2020-01-02 10:22:40 +01:00
Christian Schneppe
8ca04b46e6
persist file size across aborts 2020-01-02 09:43:51 +01:00
Christian Schneppe
a5f52ef599
make parts in Message.java final 2020-01-02 09:05:49 +01:00
Christian Schneppe
1657e678ac
automatically set year in about message 2020-01-02 09:04:00 +01:00
Christian Schneppe
0f92f4a57c
Add explanations of encryption methods 2019-12-29 14:37:11 +01:00
Christian Schneppe
3fde191f63
Add files via upload 2019-12-29 14:35:22 +01:00
Christian Schneppe
b4f8f47540
display '#' in generated channel avatars 2019-12-24 14:07:15 +01:00
Christian Schneppe
15d27e2cbb
Implement download resumption for OMEMO encrypted files 2019-12-24 14:05:40 +01:00
Christian Schneppe
80c0e7d575
createOutputStream(): allow to disable decryption 2019-12-24 13:49:54 +01:00
Christian Schneppe
23cfd788b1
small contact details UI fixes 2019-12-24 13:30:54 +01:00
Christian Schneppe
99cfae2e33
rework conference and contact details
* reduce avatar size
* show big avatar on long click
* reorder elements
* show contact/muc JID in advanced mode

fixes #423
2019-12-23 14:06:52 +01:00
Christian Schneppe
07b4ea61b2
small UI refresh changes 2019-12-23 10:01:42 +01:00
Christian Schneppe
5abd54f943
show nick for /me in 1:1 chats
fixes #421
2019-12-17 11:26:59 +01:00
Christian Schneppe
b8602852b7
first step to remove OTR encryption
* make OTR available via expert settings and disable it as default
2019-12-13 21:17:01 +01:00
Christian Schneppe
dc44c346ca
rework adhocinvite 2019-12-13 21:01:00 +01:00
Christian Schneppe
65bfbafa17
use setFlags instead of addFlags to grant uri permissions 2019-12-13 17:55:24 +01:00
Christian Schneppe
51f52022cb
catch rare NPE while deleting bookmark 2019-12-13 17:20:32 +01:00
Christian Schneppe
cffb75e456
version 2.3.4 + CHANGELOG 2019-12-12 20:23:45 +01:00
Christian Schneppe
cbffac53d5
New translations strings.xml (Italian) (#419) 2019-12-12 20:18:27 +01:00
Christian Schneppe
887b14dae0
small ui fixes for private messages 2019-12-12 20:15:33 +01:00
Christian Schneppe
3a6c10bbe3
increase mam messages catchup and don't abort on MAM_MAX_MESSAGES 2019-12-12 19:44:54 +01:00
Christian Schneppe
fe080ef77a
add .heic to list of known mime types 2019-12-12 19:43:08 +01:00
Christian Schneppe
b9247bc3a7
don’t use secure delete when migrating edit column 2019-12-12 19:40:56 +01:00
Christian Schneppe
6c3565392a
small quotes fixes 2019-12-12 19:37:04 +01:00
Christian Schneppe
0c9d000689
jingle ibb: wait to receive ibb
previously we signalled succesfull file reception after receiving enough bytes on ibb;
however that causes us to race with the session-info file hash. now the recipient will wait for
<close/> and the sender will make sure to send the session-info before sending close.
2019-12-09 20:09:09 +01:00
Christian Schneppe
20b0c52e35
properly restore LMC edits. switch to LMC v1.1 2019-12-09 19:57:17 +01:00
Christian Schneppe
c1b0151474
reduce cursor size back to 4m; now that the cause is fixed 2019-12-08 15:54:43 +01:00
Christian Schneppe
6368dedc63
use Base64.NO_WRAP instead of trim() 2019-12-08 15:53:03 +01:00
Christian Schneppe
fe6724887d
show jabber accounts from local address in Quicksy flavor 2019-12-08 15:49:33 +01:00
Christian Schneppe
960460e7fe
prevent crash when deleting account on servers that don't support omemo 2019-12-08 15:43:55 +01:00
Christian Schneppe
e0e158e5fc
increase cursor window size on Android P when restoring messages 2019-12-08 15:43:21 +01:00
Christian Schneppe
31c16b0f8f
delete cached posh file after not being able to verify 2019-12-08 15:40:58 +01:00
Christian Schneppe
32b1ab2b3b
only show message deleted if LMC is activated 2019-12-08 15:37:29 +01:00
Christian Schneppe
d6652c571d
add direct answer button in private muc messages 2019-12-08 15:12:37 +01:00
Christian Schneppe
4f0ac410eb
color optimizations in orange theme 2019-12-08 14:15:01 +01:00
Christian Schneppe
6c445cc68a
use current time on resend message 2019-12-08 13:59:41 +01:00
Christian Schneppe
ad32a6889b
permission compatibility for Android 10 2019-12-08 13:45:44 +01:00
Christian Schneppe
5d5b7010b1
version 2.3.3 2019-12-08 10:28:48 +01:00
Christian Schneppe
6084fd91d4
try to fix missing sendbutton 2019-12-08 10:25:36 +01:00
Christian Schneppe
074e7311bf
version 2.3.2 2019-12-08 09:49:17 +01:00
Christian Schneppe
2cdc394d52
fix shareWithActivity 2019-12-08 09:48:44 +01:00
Christian Schneppe
b5c5625e78
don't use versionNameSuffix for git/fdroid version 2019-12-07 21:51:06 +01:00
Christian Schneppe
d828d5b4a9
version 2.3.1 + changes 2019-12-07 21:31:10 +01:00
Christian Schneppe
e66ffc6280
Merge branch 'master' of https://github.com/kriztan/Pix-Art-Messenger 2019-12-07 21:30:46 +01:00
Christian Schneppe
cd1d3cebee
New Crowdin translations (#409)
* New translations strings.xml (Ukrainian)

* New translations strings.xml (Ukrainian)

* New translations strings.xml (Italian)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Chinese Simplified)
2019-12-07 21:30:35 +01:00
Christian Schneppe
d8d37876b0
fix crash on android < 24 2019-12-07 21:18:57 +01:00
Christian Schneppe
8358a40792
fix rare crash 2019-12-07 21:15:07 +01:00
Christian Schneppe
7ee4de1a27
add missing permission requests for Android P
possibly fixes #410
2019-12-07 21:11:56 +01:00
Christian Schneppe
a80432f671
fix updater 2019-12-07 20:14:45 +01:00
Christian Schneppe
348498b875
fix crash on Android 4 devices 2019-12-07 20:09:47 +01:00
Christian Schneppe
539302ee36
don't show real username in quotes
fixes #415
2019-12-07 20:05:52 +01:00
FH
587a719e85 Disable NewLines in Base64 values (#416)
Smack gets confused and throws NullPointerException
when Base64 contains newlines. Therefor disable newlines
in Base64. I assume newlines in Base64 are also not
expected by other implementations.
2019-12-03 08:04:38 +01:00
genofire
062371dd4c fix(circleci): after rename flavor artifacts changes (#412) 2019-11-20 14:50:13 +01:00
Christian Schneppe
6690201ede
fix user in textselection quotes 2019-11-17 20:17:54 +01:00
Christian Schneppe
c0c45b4eab
version 2.3.0 + changes 2019-11-16 18:15:07 +01:00
Christian Schneppe
5d83478952
New Crowdin translations (#408)
* New translations strings.xml (Italian)

* New translations strings.xml (Italian)

* New translations strings.xml (Arabic)

* New translations strings.xml (Portuguese)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Bulgarian)

* New translations strings.xml (Spanish)

* New translations strings.xml (Russian)

* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (Polish)

* New translations strings.xml (Italian)

* New translations strings.xml (German)

* New translations strings.xml (French)

* New translations strings.xml (Dutch)

* New translations strings.xml (Chinese Traditional)

* New translations strings.xml (Catalan)

* New translations strings.xml (Ukrainian)

* New translations strings.xml (Spanish)

* New translations strings.xml (Portuguese)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (German)

* New translations strings.xml (Chinese Traditional)
2019-11-16 18:13:17 +01:00
Christian Schneppe
7ea00aa98c
add possibility to block muc user from muc 2019-11-16 18:04:58 +01:00
Christian Schneppe
14081b1f85
set omemo on as default 2019-11-16 15:26:50 +01:00
Christian Schneppe
d66581e9e6
fix some wordings 2019-11-16 15:25:41 +01:00
Christian Schneppe
9213106193
fix color mixing in ChannelDiscoveryActivity 2019-11-16 15:22:57 +01:00
Christian Schneppe
123fd1926f
move direct search from expert setting to UI 2019-11-16 15:22:21 +01:00
Christian Schneppe
0d649e48bb
rework design colors and make it compatible with older ROMs 2019-11-16 15:11:29 +01:00
Christian Schneppe
da925991c1
make exporting backup compatible with conversations 2019-11-16 11:27:30 +01:00
Christian Schneppe
74251e61d4
version 2.2.10.beta (2019-11-15) 2019-11-15 22:04:13 +01:00
Christian Schneppe
7abea021ce
version 2.2.10.beta (2019-11-15) + changes 2019-11-15 22:02:31 +01:00
Christian Schneppe
5c7d6141e0
Merge branch 'master' of https://github.com/kriztan/Pix-Art-Messenger 2019-11-15 22:00:59 +01:00
Christian Schneppe
5aae0c8047
New Crowdin translations (#407)
* New translations strings.xml (Catalan)

* New translations strings.xml (Chinese Traditional)

* New translations strings.xml (German)

* New translations strings.xml (Italian)

* New translations strings.xml (Spanish)
2019-11-15 21:59:35 +01:00
Christian Schneppe
449f09c975
fix duplicate accounts in EnterJidDialog 2019-11-15 21:53:21 +01:00
Christian Schneppe
78c85da03c
introduce new orange theme color 2019-11-15 21:30:32 +01:00
TheOneric
ba225074d5 Fix empty grey box for unknown file types (#405)
Previously if files, whose mime-type was not recognized by MimeUtils, were being sent to a Pix-Art-Client only a grey box without text would be shown.
With this commit the general "open file" text is now shown for these files. Previously this text was only shown for files with recognized mime-types, not being specially handled (like images).

Also the Advanced SSA (*.ass) subtitle format was added to MimeUtils as 'text/plain'.
2019-11-15 18:33:58 +01:00
Christian Schneppe
60b96381f3
fix CircleCI 2019-11-15 17:31:17 +01:00
Christian Schneppe
5c26aec10d
make private messages in MUCs more visible 2019-11-15 17:14:11 +01:00
Christian Schneppe
345e2b7da1
rework no write access hint in moderated public mucs 2019-11-15 17:14:10 +01:00
Christian Schneppe
6c083786f1
small UI fixes in SetSettingsActivity 2019-11-15 17:14:10 +01:00
Christian Schneppe
8319822427
make presence dialog scrollable to fit small screens 2019-11-15 17:14:10 +01:00
Christian Schneppe
25babd117d
do not crash when audio file reports zero length 2019-11-15 17:14:10 +01:00
Christian Schneppe
88948711ff
prevent crash when counterpart in message was null 2019-11-15 17:14:09 +01:00
Christian Schneppe
cccc24c8e2
check if sender is in contact list before accepting jingle file 2019-11-15 17:14:09 +01:00
Christian Schneppe
f5d5be2b3c
allow jingle state transition for fallback after proxy failure 2019-11-15 17:14:09 +01:00
Christian Schneppe
b73774260a
make jingle state transitions less error prone 2019-11-15 17:14:09 +01:00
Christian Schneppe
528649cd70
update libs and gradle 2019-11-15 17:14:09 +01:00
Christian Schneppe
10a29f53a6
fixed spinning wheel when switching between local and jabber.network discovery 2019-11-15 17:14:08 +01:00
Christian Schneppe
533076813b
do not validate port if hostname is empty 2019-11-15 17:14:08 +01:00
Christian Schneppe
f42f00b40e
rework IntroActivity a bit 2019-11-15 17:14:08 +01:00
TheOneric
6a8fb3e8ae English README (#406)
Add an English translation of README.md and link it on and with the german version.
This addresses #228
2019-11-12 11:18:19 +01:00
Christian Schneppe
904cc18ec2
show overlay to set avatar at the right bottom corner 2019-11-10 14:55:53 +01:00
Christian Schneppe
189ca534ac
set isQuicksy true 2019-11-09 22:30:35 +01:00
Christian Schneppe
4d6a78b005
use variable for app directory in FileBackend 2019-11-06 19:58:12 +01:00
Christian Schneppe
019e673ce7
remove schulchat from main project 2019-11-06 19:08:51 +01:00
Christian Schneppe
1b9c48dbad
rework message deletion 2019-11-04 21:05:49 +01:00
Christian Schneppe
d66b0d010e
small UI fixes 2019-11-03 19:53:43 +01:00
Christian Schneppe
0a6879eea5
fixed links in privacy warning in channel discovery 2019-11-03 19:44:07 +01:00
Christian Schneppe
7d7835e035
add grace period 2019-11-03 19:42:01 +01:00
Christian Schneppe
ad75ab3f68
optionally search local muc rooms instead of jabber.network 2019-11-03 19:41:16 +01:00
Christian Schneppe
6a672b4aac
mark silent notifications as local only
this will prevent silent notifications (for example those supressed by grace period) showing up on my smart watch
2019-11-03 19:10:09 +01:00
Christian Schneppe
78ef54d600
catch security exception when passing on share intent that didn't give us permission 2019-11-03 19:08:29 +01:00
Christian Schneppe
94b1f705c5
change apk names 2019-11-03 19:05:31 +01:00
Christian Schneppe
d54b870971
change welcome screen and splash screen for schulchat 2019-11-02 17:55:48 +01:00
Christian Schneppe
d69f35db35
change schulchat logo a bit 2019-11-02 17:54:44 +01:00
Christian Schneppe
cdcc4979ec
set some colors for schulchat 2019-11-02 17:38:06 +01:00
Christian Schneppe
87345e410e
move raw logo image into the correct art folder 2019-11-02 17:24:40 +01:00
Christian Schneppe
1c0157d2ab
create a simple icon for schulchat 2019-11-02 17:19:51 +01:00
Christian Schneppe
5d67ddc1b1
disable encryption for schulchat by default 2019-11-02 17:19:18 +01:00
Christian Schneppe
06949b7649
introduce setting to completely disable (OMEMO) encryption 2019-11-02 17:18:45 +01:00
Christian Schneppe
f81d60f9a8
change Pix-Art Messenger to Schulchat Messenger in strings 2019-11-02 16:42:27 +01:00
Christian Schneppe
b1f1c773f8
move some more config variables to match different app modes 2019-11-02 16:39:13 +01:00
Christian Schneppe
d69583c3f3
move some more config variables to match different app modes 2019-11-02 16:07:46 +01:00
Christian Schneppe
7bb48465e2
rename build flavors 2019-11-02 15:44:31 +01:00
Christian Schneppe
6e386a8701
use separate file for about info 2019-11-02 14:37:55 +01:00
Christian Schneppe
267f920208
add schulchat buildflavor 2019-11-02 14:03:07 +01:00
Christian Schneppe
1f7259407f
show attachment button in private muc messages 2019-10-27 20:15:26 +01:00
Christian Schneppe
ccc1254896
version 2.2.10 beta (2019-10-26) + changes 2019-10-27 01:59:01 +02:00
Christian Schneppe
c313ffadec
getAdHocInviteUri async 2019-10-27 01:58:27 +02:00
Christian Schneppe
f2ceba6a90
fix crash in SearchActivity 2019-10-27 01:21:36 +02:00
Christian Schneppe
6929196af0
add user to roster on avatar long press 2019-10-27 01:09:46 +02:00
Christian Schneppe
131102939c
open video if internal player fails 2019-10-27 00:10:22 +02:00
Christian Schneppe
0389f67482
attempt to fix some rare crashes 2019-10-26 20:09:28 +02:00
Christian Schneppe
c85b487fca
store message bodies up to 1MB 2019-10-26 19:40:21 +02:00
Christian Schneppe
001da15a33
catch all exceptions when closing closable 2019-10-26 19:39:41 +02:00
Christian Schneppe
bbc79c5ee1
catch dead system exception when creating error notification 2019-10-26 19:37:55 +02:00
Christian Schneppe
e336d96890
fixed loading channel results from cache 2019-10-26 19:35:17 +02:00
Christian Schneppe
824ba44fd4
don’t mark pgp encrypted files received from dino as deleted 2019-10-26 19:33:26 +02:00
Christian Schneppe
8e2adfcfd1
fix some issues 2019-10-26 19:31:19 +02:00
Christian Schneppe
aac524da7e
clear bitmap cache after changing avatar settings 2019-10-26 19:29:57 +02:00
Christian Schneppe
1076c25767
always show 'contact details' on avatar long press in non-anon 2019-10-26 19:06:50 +02:00
Christian Schneppe
ac86587323
flush on socks connection 2019-10-26 19:04:59 +02:00
Christian Schneppe
e5cb9b1e17
properly guard bookmarks2 deletion 2019-10-26 19:03:47 +02:00
Christian Schneppe
5ac2a42d13
trigger omemo self healing for live msgs on server w/o MAM 2019-10-26 19:02:55 +02:00
Christian Schneppe
7d8514492a
set autojoin=true after following invite 2019-10-26 19:01:32 +02:00
Christian Schneppe
493ac6286f
update ui after bookmark change 2019-10-26 18:28:35 +02:00
Christian Schneppe
57e0f4a21d
bookmarks2. introduce #compat namespace 2019-10-26 18:28:26 +02:00
Christian Schneppe
004867686b
leave/join on bookmark modifactions 2019-10-26 18:13:10 +02:00
Christian Schneppe
9f45e2509e
support for purge and delete 2019-10-26 18:10:56 +02:00
Christian Schneppe
35df965b58
Bookmarks2: support retraction 2019-10-26 18:10:48 +02:00
Christian Schneppe
8d7727fcdc
support for delete bookmarks2 2019-10-26 17:53:03 +02:00
Christian Schneppe
6df9c91b30
WIP Bookmarks 2 support 2019-10-26 17:48:13 +02:00
Christian Schneppe
5e41a659b7
LMC: find replacedMessages based on bare JID 2019-10-26 17:22:25 +02:00
Christian Schneppe
82639b94b7
show reason in error message 2019-10-26 17:21:21 +02:00
Christian Schneppe
7c8b91325e
disable context menu on failed jingle files 2019-10-26 17:20:48 +02:00
Christian Schneppe
3b6b25720f
mark cancelled jingle ft as such on both sides 2019-10-26 17:19:06 +02:00
Christian Schneppe
41e0559533
fully read port in socks connection
incoming direct connections in receive mode wouldn’t clear the entire
destination from the input stream; thus adding a leading 0x00 to the file
2019-10-26 16:52:31 +02:00
Christian Schneppe
25eb08ae48
set shorter timeouts when using direct candidates 2019-10-26 16:50:56 +02:00
Christian Schneppe
c488121d38
hide 'use integrated emoji' setting for Android O and higher 2019-10-19 22:01:44 +02:00
Christian Schneppe
4ca1c1420f
hide 'use integrated emoji' setting for Android O and higher 2019-10-19 22:01:25 +02:00
Christian Schneppe
cf2451fa8c
make export of chats as txt working again
fixes #402
2019-10-19 21:28:23 +02:00
Christian Schneppe
2c2bb185c3
change lib to fix crash on startup for older devices
fixes #398
2019-10-19 21:17:18 +02:00
Christian Schneppe
5a3f721eb4
catch exception while setting useragent 2019-10-19 21:07:05 +02:00
Christian Schneppe
6a4b714e10
show "no results" if there where no channels found in channel discovery search 2019-10-03 19:58:18 +02:00
Christian Schneppe
4f4683e052
Use a more meaningful name for old status 2019-10-02 15:46:41 +02:00
Christian Schneppe
40446c25d9
Use dark navigation bar in QR scanner activity
* Use dark navigation bar on dark theme

This approach uses `tools:targetApi` instead of separate theme file and
avoids lint errors.

* Use dark navigation bar in QR scanner activity

This is consistent with the black background that is already used in
that activity.
2019-10-02 15:44:55 +02:00
Christian Schneppe
d76882631c
use new jabber.search.network endpoint 2019-10-02 15:39:42 +02:00
Christian Schneppe
616eba9d1f
fix NPE when using channel search and DOMAIN_LOCK 2019-10-02 15:38:16 +02:00
Christian Schneppe
60ca56c188
report not-acceptable on jingle errors 2019-10-02 15:36:22 +02:00
Christian Schneppe
cb0be6f06a
do not set invideous as default and add config to first start settings screen 2019-10-02 15:30:26 +02:00
Christian Schneppe
ae45a3e55b
version 2.2.10 beta (2019-09-29) 2019-09-29 15:53:51 +02:00
Christian Schneppe
ee84b68caf
New Crowdin translations (#396)
* New translations strings.xml (Russian)

* New translations strings.xml (German)

* New translations strings.xml (Spanish)

* New translations strings.xml (Portuguese)

* New translations strings.xml (Italian)

* New translations strings.xml (Chinese Traditional)

* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (Chinese Simplified)
2019-09-29 15:42:34 +02:00
Christian Schneppe
89781841ac
delete problematic language file 2019-09-29 15:30:58 +02:00
Christian Schneppe
7375f24041
account deletion: only attempt to delete omemo id when connected 2019-09-29 14:19:30 +02:00
Christian Schneppe
d852745b2c
show resource prompt when sending uncompressed video 2019-09-29 14:18:14 +02:00
Christian Schneppe
c5c8ba4f31
delete omemo keys when deleting account 2019-09-29 14:06:53 +02:00
Christian Schneppe
f3a821d11e
make list selection manager work with app compat 2019-09-29 14:04:38 +02:00
Christian Schneppe
72f2b8650e
rework intro 2019-09-29 13:57:58 +02:00
Christian Schneppe
2565da03bb
channel search result long press to show join dialog 2019-09-24 20:41:17 +02:00
Christian Schneppe
5aa83a0db4
version 2.2.10 beta (2019-09-24) + changes 2019-09-24 20:33:54 +02:00
Christian Schneppe
0fec7bcea2
New Crowdin translations (#392)
* New translations strings.xml (Basque)

* New translations strings.xml (Bulgarian)

* New translations strings.xml (Catalan)

* New translations strings.xml (Cebuano)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Turkish)

* New translations strings.xml (Chinese Traditional)

* New translations strings.xml (Portuguese)

* New translations strings.xml (German)

* New translations strings.xml (Italian)

* New translations strings.xml (Italian)

* New translations strings.xml (German)

* New translations strings.xml (Spanish)

* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (Italian)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (German)

* New translations strings.xml (Portuguese)

* New translations strings.xml (Chinese Traditional)

* New translations strings.xml (German)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Portuguese, Brazilian)
2019-09-24 20:31:02 +02:00
Christian Schneppe
9ce21a8077
small changes in quotes
* don't show username in single chats
* differ between own and foreign quotes
2019-09-24 20:17:10 +02:00
Christian Schneppe
59f392a17a
minor intro improvements, shorten some texts 2019-09-24 19:54:03 +02:00
Christian Schneppe
7286bf019e
fix crash OTR crash 2019-09-24 19:52:28 +02:00
Christian Schneppe
443925d2e2
correct file locations for EmojiService 2019-09-23 20:50:35 +02:00
Christian Schneppe
104c02dc33
fix missing deps for EmojiService 2019-09-23 20:38:25 +02:00
Christian Schneppe
80e61023c5
Version 2.2.10 beta (2019-09-23) 2019-09-23 20:32:36 +02:00
Christian Schneppe
bf260e9341
New Crowdin translations (#391)
* New translations strings.xml (Russian)

* New translations strings.xml (German)

* New translations strings.xml (Swedish)

* New translations strings.xml (Spanish)

* New translations strings.xml (Romanian)

* New translations strings.xml (Portuguese, Brazilian)

* New translations strings.xml (Polish)

* New translations strings.xml (Italian)

* New translations strings.xml (Indonesian)

* New translations strings.xml (Galician)

* New translations strings.xml (French)

* New translations strings.xml (Filipino)

* New translations strings.xml (Dutch)

* New translations strings.xml (Czech)

* New translations strings.xml (Chinese Traditional)
2019-09-23 20:31:34 +02:00
Christian Schneppe
8b8d17900e
Google Play version use downloadable Emojis instead of bundled 2019-09-23 20:21:08 +02:00
Christian Schneppe
9fd788bf7e
print emoji only status messages larger 2019-09-23 19:46:02 +02:00
Christian Schneppe
b0347ba929
do not parse invites from type=groupchat 2019-09-23 19:43:20 +02:00
Christian Schneppe
5bf81ac737
update CHANGELOG 2019-09-22 15:18:35 +02:00
Christian Schneppe
523f5a14a2
show username as title in context menu when long clicking avatar in MUC 2019-09-22 15:12:16 +02:00
Christian Schneppe
541ac49fed use existing variable for user 2019-09-22 10:48:21 +02:00
Christian Schneppe
c5c9c92b26 show context menu in channel search to share uri 2019-09-21 12:43:41 +02:00
Christian Schneppe
5289ddd6d8 update license info 2019-09-21 12:23:51 +02:00
Christian Schneppe
041351b176 introduce app intro and some help screens 2019-09-21 12:22:32 +02:00
Christian Schneppe
82ff5d1238 update more libs 2019-09-21 12:14:48 +02:00
Christian Schneppe
9f7fcd1a47 close correct socket after faulty jingle socks connection 2019-09-20 23:58:04 +02:00
Christian Schneppe
c75fbc7e57 improved logging for messages waiting for join 2019-09-20 23:57:29 +02:00
Christian Schneppe
346b272bac update libraries 2019-09-20 23:54:45 +02:00
Christian Schneppe
c0c1af1cca added overlay to avatar in EditAccountActivity
in addition to #390
2019-09-20 23:53:07 +02:00
Christian Schneppe
008d289514 fix strings 2019-09-20 19:54:17 +02:00
Christian Schneppe
b26b5d01fe migrate to AndroidX 2019-09-20 17:54:28 +02:00
Christian Schneppe
e0989e7a82 replace YouTube links with Invidious links
increases privacy and is configurable via preferences
2019-09-20 17:34:00 +02:00
Christian Schneppe
639f3c485b set new automatic theme switch as default 2019-09-19 12:33:22 +02:00
Christian Schneppe
1c9b10c79d bug fixes in MessageAdapter 2019-09-19 12:29:11 +02:00
Christian Schneppe
f976ebae8a fixed R8 weirdness 2019-09-19 11:53:38 +02:00
Christian Schneppe
28a2686620 fixed direct invites after adhoc 2019-09-19 11:50:37 +02:00
Christian Schneppe
701b8617de cancel spinning wheel on muclumbus error 2019-09-19 11:47:19 +02:00
Christian Schneppe
517eff346f warn when using _only_ ambiguous cyrillic 2019-09-19 11:44:44 +02:00
Christian Schneppe
53e19a2e9c fixed some minor NPE 2019-09-19 11:42:30 +02:00
Christian Schneppe
03530f667d clear notifications when deleting account 2019-09-19 11:37:16 +02:00
Christian Schneppe
2a55037993 do not finish or repair sessions for untrusted senders
finishing (sending a key transport message in response to pre key message) as
well as reparing sessions will leak resource and availability and might in
certain situations in group chat leak the Jabber ID.

Therefor we disable that. Leaking resource might not be considered harmful by
a lot of people however we have always doing similar things with receipts.
2019-09-19 11:35:17 +02:00
Christian Schneppe
f65fa8fb0b changed profile view
fixes #390, #369
2019-09-19 11:29:33 +02:00
Christian Schneppe
62209dc7ac parse LMC 1.1 2019-09-13 21:39:41 +02:00
Christian Schneppe
eb34cdc3e3 keep track of previously edited ids 2019-09-13 21:38:18 +02:00
Christian Schneppe
1b45c33393 catch empty password exceptions in ExportBackupService 2019-09-13 21:18:26 +02:00
Christian Schneppe
b37741a0ae stopped constantly focus shift if user is typing
fixes #386
2019-09-13 20:37:27 +02:00
Christian Schneppe
092beba1ba set Messenger identity as userAgent for RichPreview and ChannelDiscovery 2019-09-12 20:42:35 +02:00
Christian Schneppe
88574e39d2 when parsing omemo messages ensure we only find one element 2019-09-12 19:46:20 +02:00
Christian Schneppe
8857f9341c inherit language from parent message when finding localized body 2019-09-12 19:44:51 +02:00
Christian Schneppe
85b8ec7702 show language in message bubble if multiple language variants were received
XML and by inheritence XMPP has the feature of transmitting multiple language
variants for the same content. This can be really useful if, for example, you
are talking to an automated system. A chat bot could greet you in your own
language.

On the wire this will usually look like this:

```xml
<message to="you">
  <body>Good morning</body>
  <body xml:lang="de">Guten Morgen</body>
</message>
```

However receiving such a message in a group chat can be very confusing and
potentially dangerous if the sender puts conflicting information in there and
different people get shown different strings.

Disabling support for localization entirely isn’t an ideal solution as on
principle it is still a good feature; and other clients might still show a
localization even if Conversations would always show the default language.

So instead we now show the displayed language in a corner of the
message bubble if more than one translation has been received.

If multiple languages are received we will attempt to find one in
the language the operating system is set to. If no such translation can be
found it will attempt to display the English string.

If English can not be found either (for example a message that only has ru and
fr on a phone that is set to de) it will display what ever language came first.

Furthermore we will discard (not show at all) messages with with
multiple bodies of the same language. (This is considered an invalid message)

The language tag will not be shown if we receive a single body in
a language not understood by the user. (For example operating system set to
'de' and message received with one body in 'ru' will just display that body as
usual.)

As a guide line to the user: If you are reading a message where it is important
that this message is not interpreted differently by different people (like a
vote (+1 / -1) in a chat room) make sure it has *no* language tag.
2019-09-12 19:44:01 +02:00
Christian Schneppe
0abffde5ef fixed 2 issues reported by new linter 2019-09-12 19:24:16 +02:00
Christian Schneppe
f7da662e6a make some more activities to start as single task 2019-09-11 20:27:49 +02:00
Christian Schneppe
bc272651e2 introduce automatic theme based on the systems theme 2019-09-11 20:27:07 +02:00
Christian Schneppe
911b537f34 include user into quote message 2019-09-11 19:18:56 +02:00
Christian Schneppe
41f36bd816 add 1080p as video resolution and set default to 720p 2019-09-09 21:38:37 +02:00
Christian Schneppe
3334ab7d0a introduce new file transfer status 2019-09-09 21:13:06 +02:00
Christian Schneppe
9da5429893 update libs and CHANGELOG 2019-09-09 20:46:06 +02:00
Christian Schneppe
c9a81b53b3 do not include scope in ipv6 annoucment 2019-09-09 20:18:38 +02:00
Christian Schneppe
ea4b999d8a include ticker information in notification 2019-09-09 20:07:13 +02:00
Christian Schneppe
1584df8ac7 fix permission errors 2019-09-09 19:44:26 +02:00
Christian Schneppe
8427f377ea catch exception in updater 2019-09-09 19:44:13 +02:00
Christian Schneppe
5606b32db7 version 2.2.9 + changes 2019-09-08 09:39:57 +02:00
Christian Schneppe
7aaca90e41 fix crash during file existing check 2019-09-08 09:39:45 +02:00
Christian Schneppe
0559e801fa version 2.2.8 + changes 2019-09-08 00:04:39 +02:00
Christian Schneppe
fce79f08c1 improve webpreviews during offline times 2019-09-07 23:58:56 +02:00
Christian Schneppe
41aecb003d send multiple downloads and uploads into queue 2019-09-07 23:55:48 +02:00
Christian Schneppe
e93057cfce Merge branch 'master' of https://github.com/kriztan/Pix-Art-Messenger 2019-09-07 21:39:21 +02:00
Christian Schneppe
46232857fc catch exceptions in SerialSingleThreadExecutor 2019-09-07 21:39:09 +02:00
Christian Schneppe
1b58f476c9
New Crowdin translations (#384)
* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Russian)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Spanish)

* New translations strings.xml (Italian)

* New translations strings.xml (German)

* New translations strings.xml (Russian)

* New translations strings.xml (German)

* New translations strings.xml (Spanish)

* New translations strings.xml (Portuguese)

* New translations strings.xml (Polish)

* New translations strings.xml (Italian)

* New translations strings.xml (French)

* New translations strings.xml (Dutch)

* New translations strings.xml (Chinese Traditional)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Bulgarian)

* New translations strings.xml (Catalan)

* New translations strings.xml (German)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Chinese Simplified)

* New translations strings.xml (Spanish)
2019-09-07 14:52:33 +02:00
Christian Schneppe
079777d9dd update gradle 2019-09-06 22:56:32 +02:00
Christian Schneppe
e07828271d shorten restore_warning 2019-09-06 22:40:16 +02:00
Christian Schneppe
361eec24a9 JET uses plain text file size 2019-09-06 22:12:41 +02:00
Christian Schneppe
1322031176 do not include link local in direct candidates 2019-09-06 22:11:04 +02:00
Christian Schneppe
75548eafa4 make Tor connections work with direct TLS 2019-09-06 22:07:20 +02:00
Christian Schneppe
c7629a6093 implemented support for for jingle encrypted transports (XEP-0396) 2019-09-06 22:01:42 +02:00
Christian Schneppe
f8a94ec52e deleted wrong to jid
fixes #374
2019-09-06 21:51:30 +02:00
Christian Schneppe
b33eaff4f4 check for orphaned files 2019-09-06 21:49:09 +02:00
Christian Schneppe
a2ff79a4c3 fix versionCode 2019-09-03 20:59:01 +02:00
Christian Schneppe
8e854ba29d version 2.2.8 beta (2019-09-03) + changes 2019-09-03 20:33:31 +02:00
Christian Schneppe
21ecf54e37 get images from google photos 2019-09-03 20:32:01 +02:00
Christian Schneppe
be82ac5633 fix some crashes 2019-09-03 20:31:32 +02:00
Christian Schneppe
4aa24e84c4 use higher priority for proxy on receiving end 2019-09-03 19:59:51 +02:00
Christian Schneppe
9cd6fa5097 send fallback to ibb after proxy activation failed 2019-09-03 19:57:29 +02:00
Christian Schneppe
f36ff9640d also reply with direct connections on response 2019-09-03 19:56:00 +02:00
Christian Schneppe
71e4bcf9cb bare minimum direct connections 2019-09-03 19:50:42 +02:00
Christian Schneppe
edcfdb974d order canditates by priority before attempting to connect 2019-09-03 19:43:29 +02:00
Christian Schneppe
ae2a4ed495 fix strings 2019-09-03 19:33:34 +02:00
Christian Schneppe
7da9909586 fix rotation issues in MediaViewerActivity 2019-08-31 21:55:36 +02:00
Christian Schneppe
e612506be3 when sharing text from PAM to PAM use quotes 2019-08-31 15:50:13 +02:00
Christian Schneppe
506b092821 more ibb fixes (include sid in transport-accept) 2019-08-31 15:47:29 +02:00
Christian Schneppe
987744bb25 fixed unlikely race between enabling carbons and discovering last MAM id 2019-08-31 15:44:48 +02:00
Christian Schneppe
cce36fcbb7 include pgp sig and status in presence to non anon muc 2019-08-31 15:43:20 +02:00
Christian Schneppe
24ca2a88fb do not put default nick into bookmark if none has been set before 2019-08-31 15:41:38 +02:00
Christian Schneppe
a1ade22308 fixed pgp decryption of automatically accepted jingle ft 2019-08-31 15:39:31 +02:00
Christian Schneppe
4323b7de5a increased reconnection interval after policy violation 2019-08-31 15:35:03 +02:00
Christian Schneppe
240addee71 refactored filename and extension parsing 2019-08-31 15:34:17 +02:00
Christian Schneppe
ca21a38cb6 catch npe on participants context menu 2019-08-31 15:21:36 +02:00
Christian Schneppe
4142360189 make config flag for leave before join 2019-08-31 15:20:44 +02:00
Christian Schneppe
016124b548 create empty disco result on error to fire advance stream features event 2019-08-31 15:19:20 +02:00
Christian Schneppe
6c3b5defa3 show scrollbars in muc user screen 2019-08-31 15:17:16 +02:00
Christian Schneppe
7610493b1e fix SOCKS5 to IBB fallback 2019-08-31 15:16:27 +02:00
Christian Schneppe
611f28fbd0 catch more firebase library bugs 2019-08-31 15:13:53 +02:00
Christian Schneppe
b519d6370b fixed send_multiple share intent with empty extras 2019-08-31 15:13:06 +02:00
Christian Schneppe
6d83b098e6 catch IllegalArgumentException when reading backup file 2019-08-31 15:12:04 +02:00
Christian Schneppe
a65e9edef1 implement time out for waiting on voice recording 2019-08-31 15:10:40 +02:00
Christian Schneppe
75923a1835 made domain verifier case insensitive. 2019-08-31 15:04:51 +02:00
Christian Schneppe
9acde9e105 put initial xmpp uri into signup intent 2019-08-31 15:02:35 +02:00
Christian Schneppe
bc7c378080 resetToWaiting should include http uploaded files 2019-08-31 14:59:26 +02:00
Christian Schneppe
4210498d3c do not include DNS servers from networks know to be inactive
* we still include DNS servers from VPNs because of edge cases where the XMPP server is hosted in the VPN
* on older Android versions we don’t know if a network is active or not (activeNetwork == null)
2019-08-31 14:57:23 +02:00
Christian Schneppe
58b464cf26 catch fcm library bugs 2019-08-31 14:54:53 +02:00
Christian Schneppe
8b63dbb26d do password empty check in dialog not in restore backup service 2019-08-31 14:52:57 +02:00
Christian Schneppe
94143682ff open backup files on view action 2019-08-31 14:51:02 +02:00
Christian Schneppe
47c29e5842 allow backup to be restored from selected file 2019-08-31 14:45:36 +02:00
Christian Schneppe
f7c293387b fix missing import
maybe this fixes #366
2019-08-31 14:32:45 +02:00
Christian Schneppe
dd627f76bf make short vibrate in open chat configurable and respect phone silent mode 2019-08-31 14:31:31 +02:00
Christian Schneppe
037cd4e95b create share button in backup done notification 2019-07-17 21:10:26 +02:00
Christian Schneppe
fa07274d2d split on first dot when using domain instead of black listed local part 2019-07-17 20:50:03 +02:00
Christian Schneppe
6e93f698a1 muc message corrections only compare bare jid true counterpart
during live messages we only store the bare real jid; on muc catch up we might get the full jid
for that reason we only compare bare jids
2019-07-17 20:46:34 +02:00
Christian Schneppe
1f77d6b8cd prefer attachment in share intent if there is one 2019-07-17 20:45:52 +02:00
Christian Schneppe
f42d144a66 updated retrofit 2019-07-17 20:44:55 +02:00
Christian Schneppe
d52f09b6f4 handle blocking and unblocking of full jids 2019-07-17 20:44:34 +02:00
Christian Schneppe
c88523b1b8 use helper method to close socket 2019-07-17 20:42:54 +02:00
Christian Schneppe
00334edc6f disable muc push on archive instead of leave
leave can be triggered in conference details and doesn’t mean we don’t want pushes
2019-07-03 11:49:46 +02:00
Christian Schneppe
7d55a62328 check if activity is not null before using it to paint send button 2019-07-03 11:38:29 +02:00
Christian Schneppe
e8a4eaf8cd correct webpreview cache paths 2019-07-03 11:37:35 +02:00
Christian Schneppe
f2c4eebaf8 migrate copy ond write list to synchronized hashset for pending mucs 2019-07-01 08:52:56 +02:00
Christian Schneppe
6086d9c45f include remote server errors in errors that should trigger a self ping 2019-07-01 08:47:00 +02:00
Christian Schneppe
686c4da2b0 rate limit muc pings / joins. never run two pings at same time 2019-07-01 08:46:00 +02:00
Christian Schneppe
037932dc02 attempt to unregister when receiving push for channel no longer joined
when receiving a FCM push message for a channel the user is no longer in (this can happen when the disable command failed) an attempt will be made to explicitly unregister from the app server (which in turn will then send item-not-found on next push)
2019-07-01 08:42:54 +02:00
Christian Schneppe
66a57e0129 implement FCM push for group chats 2019-07-01 08:35:00 +02:00
Christian Schneppe
226d45a136 code cleanup & small fixes 2019-07-01 08:12:58 +02:00
441 changed files with 14962 additions and 6852 deletions

View file

@ -9,7 +9,7 @@ jobs:
key: gradle-{{ checksum "build.gradle" }}-{{ checksum ".circleci/config.yml" }}
- run: export GRADLE_USER_HOME=$PWD/.gradle
- run: echo y | sdkmanager "platforms;android-$(sed -n 's,.*compileSdkVersion\s*\([0-9][0-9]*\).*,\1,p' build.gradle)" > /dev/null
- run: ./gradlew lintStandardDebug
- run: ./gradlew lintGitDebug
- save_cache:
paths:
- .gradle/caches
@ -28,13 +28,13 @@ jobs:
- run: export GRADLE_USER_HOME=$PWD/.gradle
- run: echo y | sdkmanager "platforms;android-$(sed -n 's,.*compileSdkVersion\s*\([0-9][0-9]*\).*,\1,p' build.gradle)" > /dev/null
# build
- run: ./gradlew assembleStandard
- run: ./gradlew assembleGit
- save_cache:
paths:
- ~/.android
key: android
- store_artifacts:
path: build/outputs/apk/standard
path: build/outputs/apk/git
publish:
docker:
@ -52,7 +52,7 @@ jobs:
- run: sed -i "0,/versionName/s/^\(\s*versionName\).*/\1 \"$(printf '%s-%05d' $(git describe --tag --abbrev=0) $(git rev-list --first-parent --count HEAD))\"/" build.gradle
- run: cat -n build.gradle
# build
- run: ./gradlew assembleStandard
- run: ./gradlew assembleGit
# publish on nightly fdroid repo
- run: fdroid nightly
- save_cache:

View file

@ -1,5 +1,72 @@
### Changelog
#### Version 2.3.7
* bug fixes
#### Version 2.3.6
* bug fixes
#### Version 2.3.5
* start removing OTR
* rework conference and contact details (big avatar is available via long click) (PAM)
* resume download of OMEMO encrypted files
* channels now use '#' as symbol in avatar
* support for ?register and ?register;preauth XMPP uri parameters
* update connection settings
* use ExoPlayer for video playback (PAM)
* show artist - title for audio files (PAM)
* show PDF previews (PAM)
* minor UI improvements (PAM)
* use 12 byte IV for OMEMO
* a lot of bug fixes
#### Version 2.3.4
* fixes for Jingle IBB file transfer
* fixes for repeated corrections filling up the database
* switched to Last Message Correction v1.1
* increase mam messages catchup (PAM)
* bug fixes
#### Version 2.3.3
* fix missing send button
#### Version 2.3.2
* fix shareWithActivity
#### Version 2.3.1
* bug fixes
#### Version 2.3.0
* show name in quotes (PAM)
* introduce theme based on systems theme (PAM)
* increase default video quality (720p instead of 360p) (PAM)
* replace YouTube links with Invidious links (PAM)
* rework profile view (PAM)
* introduce app intro and some help screens (PAM)
* fixed minor security issues
* share XMPP uri from channel search by long pressing a result
* fixed OMEMO self healing (after backup restore) on servers w/o MAM
* introduce expert setting to perform channel discovery on local server instead of [search.jabber.network](https://search.jabber.network)
* introduce new orange theme color (PAM)
* bug fixes
#### Version 2.2.9
* bug fixes
#### Version 2.2.8
* stability improvements for group chats and channels
* allow backups to be restored from anywhere
* make short vibrate in open chat configurable and respect silent mode (PAM)
* fixes for Jingle file transfer
* fixed some rare crashes
* when sharing a message from and to messenger insert it as quote
* find orphaned files and show them in the chat again instead of showing them deleted (PAM)
* introduce file uploads/downloads with queue (PAM)
* fixed connection issues over Tor
* P2P file transfer (Jingle) now offers direct candidates
* support XEP-0396: Jingle Encrypted Transports - OMEMO
* bug fixes
#### Version 2.2.7
* fixing crashes
@ -8,7 +75,7 @@
* fix broken updater
#### Version 2.2.5
* make backup compatible to Conversations (only works for Android >= 8)
* make backup compatible to Conversations (only works for Android >= 8) (PAM)
* bug fix
#### Version 2.2.4
@ -18,7 +85,7 @@
* set own OMEMO devices to inactive after not seeing them for 60 days. (was 7 days)
* bug fixes for peer to peer file transfer (Jingle)
* fixed server info for unlimited/unknown max file size
* make backup compatible to Conversations
* make backup compatible to Conversations (PAM)
* performance improvements
* bug fixes

140
README-en.md Normal file
View file

@ -0,0 +1,140 @@
![logo](/art/icon.png)
# Pix-Art Messenger [![CircleCI](https://circleci.com/gh/kriztan/Pix-Art-Messenger/tree/master.svg?style=shield)](https://circleci.com/gh/kriztan/Pix-Art-Messenger/tree/master)
🇩🇪 <a href='README.md' style="font-size:150%">Deutsche Version der Readme hier verfügbar.</a>
Pix-Art Messenger is a fork of [Conversations](https://github.com/siacs/Conversations).
The changes aim to improve usability and ease transition from pre-installed and other widespread messengers. Here are some screenshots:
<img src="metadata/en-US/phoneScreenshots/00.png" width="200" /> <img src="metadata/en-US/phoneScreenshots/01.png" width="200" /> <img src="metadata/en-US/phoneScreenshots/02.png" width="200" /> <img src="metadata/en-US/phoneScreenshots/03.png" width="200" /> <img src="metadata/en-US/phoneScreenshots/04.png" width="200" /> <img src="metadata/en-US/phoneScreenshots/05.png" width="200" /> <img src="metadata/en-US/phoneScreenshots/06.png" width="200" />
(Images used were taken from http://freestockgallery.de)
## Download
Pix-Art is available for install in the F-Droid and Google-Play stores.
Alternatively release and beta-release APKs are available via github: [Releases](https://github.com/kriztan/Pix-Art-Messenger/releases/latest)
<a href='https://play.google.com/store/apps/details?id=de.pixart.messenger'><img alt='Get it on Google Play' src='https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png' height="100"/></a> <a href="https://f-droid.org/app/de.pixart.messenger"><img src="https://f-droid.org/badge/get-it-on.png" alt="Get it on F-Droid" height="100"></a>
#### Pix-Art-Messenger nightly and beta
##### F-Droid
Scan the QR-Code below and add it to your F-Droid repositories.
<img src="https://raw.githubusercontent.com/kriztan/Pix-Art-Messenger-nightly/master/icon.png" width="220">
#### Google Play
Google-Users can join the Google-Play beta program:
[Pix-Art Messenger beta](https://play.google.com/apps/testing/de.pixart.messenger)
## Social Media
<a rel="me" href="https://social.tchncs.de/@pixart">Pix-Art Messenger on Mastodon (German)</a>
There are also English and German speaking XMPP-MUCs focusing on support and development of the Pix-Art Messenger.
If you are interested in the development of the messenger, here is a MUC for you (English and German speaking):
Development-Chat: [development@room.pix-art.de](https://jabber.pix-art.de/j/development@room.pix-art.de?join)
[![Users in muc](https://inverse.chat/badge.svg?room=development@room.pix-art.de)](https://jabber.pix-art.de/j/development@room.pix-art.de?join)
There also is an Support-MUC where you can ask questions and get help with issues you may encounter, see further below for details.
## How can I support translations ?
[![Crowdin](https://d322cqt584bo4o.cloudfront.net/pix-art-messenger/localized.svg)](https://crowdin.com/project/pix-art-messenger)
Translation of in-app text is organised via [crowdin.com](https://crowdin.com/project/pix-art-messenger). You can add new languages as locales and add and edit translations already existing.
[Here is the project page on crowdin.com](https://crowdin.com/project/pix-art-messenger/invite?d=75l6j4k6k6k523f4j4m4e473u663d3m4p4t4q4)
## Help! I've encountered issues!
The easiest way to get some help is to join our support-MUC (both English and German).
Support-Chat invite link: [support@room.pix-art.de](https://jabber.pix-art.de/j/support@room.pix-art.de?join)
[![Users in muc](https://inverse.chat/badge.svg?room=support@room.pix-art.de)](https://jabber.pix-art.de/j/support@room.pix-art.de?join)
Or scan this QR-Code:
<img src="art/qr-code_suport_small.png" width="220">
If we can't fix your problem there, you can open an issue here on github, detailing your problem, how to reproduce it and provide logs. See instructions below on how to create log files.
### How to create debug logs? (adb)
#### GNU/Linux, OSX and other Unix-like systems:
1. First install The **A**ndroid **D**ebugging **B**ridge, if not already present.
###### Ubuntu / Linux Mint
```
sudo apt-get update
sudo apt-get install android-tools-adb
```
###### openSUSE 42.2 and 42.3
```
sudo zypper ref
sudo zypper install android-tools
```
###### openSUSE Tumbleweed
here you need to add the following repo (e.g. via Yast):
http://download.opensuse.org/repositories/hardware/openSUSE_Tumbleweed/
alternatively you have the option to use the `1 Click installer`
https://software.opensuse.org/package/android-tools
###### other systems
install adb using a method appropriate for your system
2. Now open a terminal in a directory of you're choice, or navigate to the directory using `cd`.
3. Follow steps [6] to [10] of the Windows instructions.
4. Start outputting your log to a file on your computer. We will be using `logcat.txt`. Enter:
```
$ adb -d logcat -v time | grep -i Pix-Art > logcat.txt
```
5. Follow the remaining steps [12] and [13] of the Windows instructions.
#### Windows:
1. Download Google's SDK-platform tools for your operating system:
https://developer.android.com/studio/releases/platform-tools.html
2. In case they were not included: You also need the ADB_drivers for your version of Microsoft Windows:
https://developer.android.com/studio/run/win-usb.html
3. Extract the zip-archive (e.g. to `C:\ADB\`)
4. Open the command line (CMD) using the start menu: Start > Execute: cmd
5. Navigate to the directory you extracted the zip to as following. We will be using `C:\ADB\`
```
c:
cd ADB
```
6. On your smartphone open the settings and search for the item `Developer Options`. If this option is not already present on your phone you will need to unlock it beforehand. To do this navigate to `Settings > About phone`, there locate `Build number` (or similar) and tap it 7-times in succession. You should now see a notification, that you are now a developer. Congrats, `Developer Options` are now available in your settings menu.
7. Inside `Developer Options` search activate the setting `USB-Debugging` (sometimes just called `Android Debugging`).
8. Connect your phone to your computer via USB cable. The necessary drivers should now be downloaded and installed if not already present. On Windows all necessary drivers should be downloaded automatically if you followed step [2] beforehand. On most GNU/Linux systems no additional action is required.
9. If everything worked out, you can now return to the command line and test if your device is being recognised. Enter `adb devices -l`; you should see output similar to:
```
> adb devices -l
List of devices attached
* daemon not running. starting it now on port 5037 *
* daemon started successfully *
042111560169500303f4 unauthorized
```
10. If your devices is labelled as `unautorized`, you must first accept a prompt on your phone asking if debugging over USB should be allowed. When rerunning `adb devices` you should now see:
```
> adb devices
List of devices attached
042111560169500303f4 device
```
11. Start outputting your log to a file on your computer. We will be using `logcat.txt` in `C:\ADB\`. Just enter the following (without `> ` into the command line):
```
> adb -d logcat -v time | FINDSTR Pix-Art > logcat.txt
```
12. Now reproduce the issue encountered.
13. Stop logging. Now take a close look at your log file and remove any personal and private information you may find before sending it together with a detailed description of your issue, instructions on how to reproduce to me. You can use GitHub's issue tracker: [Issues](https://github.com/kriztan/Pix-Art-Messenger/issues)

View file

@ -1,6 +1,8 @@
![logo](/art/icon.png)
# Pix-Art Messenger [![CircleCI](https://circleci.com/gh/kriztan/Pix-Art-Messenger/tree/master.svg?style=shield)](https://circleci.com/gh/kriztan/Pix-Art-Messenger/tree/master)
🇬🇧🇺🇸… <a href='README-en.md' style="font-size:150%">English Readme version available here</a>
Pix-Art Messenger ist eine Kopie der offiziellen Android-App [Conversations](https://github.com/siacs/Conversations) mit einigen Änderungen, insbesondere zur Verbesserung der Benutzerfreundlichkeit, um den Umstieg von oftmals vorinstallierten Messengern zu erleichtern. Die folgenden Bilder geben erste Eindrücke der App:
<img src="metadata/en-US/phoneScreenshots/00.png" width="200" /> <img src="metadata/en-US/phoneScreenshots/01.png" width="200" /> <img src="metadata/en-US/phoneScreenshots/02.png" width="200" /> <img src="metadata/en-US/phoneScreenshots/03.png" width="200" /> <img src="metadata/en-US/phoneScreenshots/04.png" width="200" /> <img src="metadata/en-US/phoneScreenshots/05.png" width="200" /> <img src="metadata/en-US/phoneScreenshots/06.png" width="200" />
@ -9,7 +11,7 @@ Pix-Art Messenger ist eine Kopie der offiziellen Android-App [Conversations](htt
Download ist hier möglich:
<a href='https://play.google.com/store/apps/details?id=de.pixart.messenger'><img alt='Get it on Google Play' src='https://play.google.com/intl/en_us/badges/images/generic/en_badge_web_generic.png' height="100"/></a> <a href="https://f-droid.org/app/de.pixart.messenger"><img src="https://f-droid.org/badge/get-it-on.png" alt="Get it on F-Droid" height="100"></a>
<a href='https://play.google.com/store/apps/details?id=de.pixart.messenger'><img alt='Jeztz bei Google Play' src='https://play.google.com/intl/en_us/badges/images/generic/de_badge_web_generic.png' height="100"/></a> <a href="https://f-droid.org/app/de.pixart.messenger"><img src="https://f-droid.org/badge/get-it-on-de.png" alt="Jetzt bei F-Droid" height="100"></a>
Alternativ kannst du den Messenger auch direkt hier von GitHub unter [Releases](https://github.com/kriztan/Pix-Art-Messenger/releases/latest) herunterladen.

BIN
art/Feature_OMEMO.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

BIN
art/schulchat/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -6,7 +6,7 @@ buildscript {
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.4.1'
classpath 'com.android.tools.build:gradle:3.6.1'
}
}
@ -33,59 +33,63 @@ repositories {
}
configurations {
standardPushImplementation
playstoreImplementation
gitImplementation
compile.exclude group: 'org.jetbrains' , module:'annotations'
}
dependencies {
implementation project(':libs:android-transcoder')
standardPushImplementation ('com.google.firebase:firebase-messaging:17.3.4') {
playstoreImplementation('com.google.firebase:firebase-messaging:20.1.3') {
exclude group: 'com.google.firebase', module: 'firebase-core'
exclude group: 'com.google.firebase', module: 'firebase-analytics'
exclude group: 'com.google.firebase', module: 'firebase-measurement-connector'
}
playstoreImplementation 'com.android.installreferrer:installreferrer:1.1.2'
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'
exclude group: 'com.android.support', module: 'exifinterface'
}
implementation 'org.bouncycastle:bcmail-jdk15on:1.58'
implementation 'org.bouncycastle:bcmail-jdk15on:1.64'
implementation 'org.jitsi:org.otr4j:0.22'
implementation 'org.gnu.inet:libidn:1.15'
implementation 'com.google.zxing:core:3.3.3'
implementation 'com.google.zxing:core:3.3.3' // > 3.3.x not working below SDK 24
implementation 'de.measite.minidns:minidns-hla:0.2.4'
implementation 'me.leolin:ShortcutBadger:1.1.22@aar'
implementation 'org.whispersystems:signal-protocol-java:2.6.2'
implementation 'com.makeramen:roundedimageview:2.3.0'
implementation 'jetty:javax.servlet:5.1.12'
implementation 'com.google.code.gson:gson:2.8.2'
implementation 'com.android.support:multidex:1.0.3'
implementation 'com.android.support:support-v13:28.0.0'
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support:exifinterface:28.0.0'
implementation 'com.android.support:support-v4:28.0.0'
implementation 'com.android.support:support-emoji:28.0.0'
implementation 'com.android.support:support-emoji-bundled:28.0.0'
implementation 'com.android.support:support-emoji-appcompat:28.0.0'
implementation 'com.android.support:exifinterface:28.0.0'
implementation 'com.android.support:design:28.0.0'
implementation 'com.android.support:cardview-v7:28.0.0'
implementation 'com.google.code.gson:gson:2.8.6'
implementation 'androidx.multidex:multidex:2.0.1'
implementation 'androidx.legacy:legacy-support-v13:1.0.0'
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.exifinterface:exifinterface:1.1.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.emoji:emoji:1.0.0'
gitImplementation 'androidx.emoji:emoji-appcompat:1.0.0'
gitImplementation 'androidx.emoji:emoji-bundled:1.0.0'
implementation 'com.google.android.material:material:1.0.0' // higher versions cause strange fab design
implementation 'androidx.cardview:cardview:1.0.0' // for compatibility
implementation 'com.davemorrissey.labs:subsampling-scale-image-view:3.10.0'
implementation project(':libs:fullscreenvideoview')
implementation 'pub.devrel:easypermissions:2.0.0'
implementation 'com.google.android.exoplayer:exoplayer-core:2.11.3'
implementation 'com.google.android.exoplayer:exoplayer-ui:2.11.3'
implementation 'pub.devrel:easypermissions:3.0.0' // version >= 3.0.0 needs android X libraries
implementation 'com.wefika:flowlayout:0.4.1'
implementation 'com.googlecode.ez-vcard:ez-vcard:0.10.5'
implementation project(':libs:xmpp-addr')
implementation 'org.hsluv:hsluv:0.2'
implementation 'org.conscrypt:conscrypt-android:1.4.2'
implementation 'org.conscrypt:conscrypt-android:2.2.1'
implementation 'pl.droidsonroids.gif:android-gif-drawable:1.2.15' // 1.2.15 is last working version for minSDK 16
implementation 'me.drakeet.support:toastcompat:1.1.0'
implementation 'org.osmdroid:osmdroid-android:6.0.3'
implementation 'com.leinardi.android:speed-dial:2.0.1'
implementation 'org.osmdroid:osmdroid-android:6.1.5'
implementation 'com.leinardi.android:speed-dial:3.1.1' // version >= 3.0.0 needs android X libraries
implementation 'com.squareup.picasso:picasso:2.71828'
implementation 'com.squareup.okhttp3:okhttp:3.12.2' // versions > 3.12.x don't support API level < 21 anymore
implementation 'com.squareup.retrofit2:retrofit:2.5.0'
implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
implementation 'com.google.guava:guava:27.1-android'
implementation 'com.squareup.okhttp3:okhttp:3.12.10' // versions > 3.12.x don't support API level < 21 anymore
implementation 'com.squareup.retrofit2:retrofit:2.6.4' //retrofit needs to stick with 2.6.x (https://github.com/square/retrofit/blob/master/CHANGELOG.md)
implementation 'com.squareup.retrofit2:converter-gson:2.6.4'
implementation 'com.google.guava:guava:28.2-android'
implementation 'com.github.AppIntro:AppIntro:5.1.0'
}
ext {
@ -95,32 +99,48 @@ ext {
android {
compileSdkVersion 28
compileSdkVersion 29
defaultConfig {
minSdkVersion 16
targetSdkVersion 28
targetSdkVersion 29
versionCode 266
versionName "2.2.7"
versionCode 299
versionName "2.3.7"
versionNameSuffix "beta_(2020-04-01)" // "-beta_(XXXX-XX-XX)"
//resConfigs "en"
archivesBaseName += "-$versionName"
//archivesBaseName += "-$versionNameSuffix" // activate for beta versions
applicationId "de.pixart.messenger"
multiDexEnabled true
buildConfigField("String", "LOGTAG", '"Pix-Art_Messenger"')
buildConfigField("String", "DOMAIN_LOCK", 'null')
buildConfigField("String", "MAGIC_CREATE_DOMAIN", '"blabber.im"')
buildConfigField("boolean", "SHOW_INTRO", 'true')
buildConfigField("String", "UPDATE_URL", '"https://xmpp.pix-art.de/Pix-Art_Messenger/update/"')
resValue "string", "applicationId", applicationId
resValue "string", "app_name", "Pix-Art Messenger"
dimension "distribution"
}
dataBinding {
enabled true
}
packagingOptions {
//X86
exclude "lib/x86/**"
//X86_64
exclude "lib/x86_64/**"
//armeabi
exclude "lib/armeabi/**"
}
dexOptions {
// Skip pre-dexing when running on Travis CI or when disabled via -Dpre-dex=false.
preDexLibraries = preDexEnabled && !travisBuild
javaMaxHeapSize "8g"
javaMaxHeapSize "4g"
jumboMode true
}
@ -132,10 +152,13 @@ android {
flavorDimensions("distribution")
productFlavors {
standardPush {
playstore {
dimension "distribution"
versionNameSuffix "-playstore"
}
git {
dimension "distribution"
}
standard
}
if (project.hasProperty('mStoreFile') &&
project.hasProperty('mStorePassword') &&

18
docs/encryption.md Normal file
View file

@ -0,0 +1,18 @@
Dear users,
Pix-Art Messenger will remove the implementation of OTR encryption by 30th June of 2020.
Unfortunately, it does not make sense to continue support on a rather out-dated technology, even as we see some users keep using it. For the moment, OTR (as well as OpenPGP) can be activated via the expert settings for advanced users. Pix-Art Messenger always tries to be usable for even [non-technical users](https://github.com/kriztan/Pix-Art-Messenger/issues/227), OTR however is not counted as appropriate for this.
Please consider to use OMEMO in the future, many other clients has implemented this, have a look at [omemo.top](https://omemo.top/). However, if you really need to continue using it, please refer to e.g. [Miranda](https://www.miranda-ng.org/de/), [Pidgin plugin](https://github.com/gkdr/lurch/), [Profanity](https://profanity-im.github.io/) or [Coy.im](https://coy.im/). You are also able to fork Pix-Art Messenger and continue the implementation on your own.
Please consider the differences between encryption protocols and also advantages of OMEMO:
<img src="https://github.com/kriztan/Pix-Art-Messenger/blob/master/art/Feature_OMEMO.png" width="400">
Source: [https://conversations.im/omemo](https://conversations.im/omemo)
Some limitations of OTR: [wikipedia.org/wiki/Off-the-Record](https://en.wikipedia.org/wiki/Off-the-Record_Messaging#Limitations)
Please inform your contacts who may also use it.
Mastodon: https://social.tchncs.de/@pixart

View file

@ -1 +1,6 @@
android.enableJetifier=true
android.useAndroidX=true
org.gradle.jvmargs=-Xmx2048M
org.gradle.parallel=true
android.enableR8=true
android.enableR8.fullMode=false

View file

@ -1,6 +1,6 @@
#Fri Apr 26 22:25:54 CEST 2019
#Fri Sep 06 22:42:08 CEST 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.2-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip

View file

@ -7,7 +7,7 @@ buildscript {
apply plugin: 'com.android.library'
dependencies {
implementation 'com.android.support:support-v13:28.0.0'
implementation 'androidx.legacy:legacy-support-v13:1.0.0'
}
android {

View file

@ -19,11 +19,9 @@ import android.media.MediaExtractor;
import android.media.MediaFormat;
import android.media.MediaMetadataRetriever;
import android.media.MediaMuxer;
import android.os.Build;
import android.util.Log;
import net.ypresto.androidtranscoder.format.MediaFormatStrategy;
import net.ypresto.androidtranscoder.utils.ISO6709LocationParser;
import net.ypresto.androidtranscoder.utils.MediaExtractorUtils;
import java.io.FileDescriptor;
@ -139,18 +137,6 @@ public class MediaTranscoderEngine {
// skip
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
String locationString = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_LOCATION);
if (locationString != null) {
float[] location = new ISO6709LocationParser().parse(locationString);
if (location != null) {
mMuxer.setLocation(location[0], location[1]);
} else {
Log.d(TAG, "Failed to parse the location metadata: " + locationString);
}
}
}
try {
mDurationUs = Long.parseLong(mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION)) * 1000;
} catch (NumberFormatException e) {

View file

@ -18,9 +18,10 @@ package net.ypresto.androidtranscoder.format;
import android.media.MediaCodecInfo;
import android.media.MediaFormat;
import android.os.Build;
import android.support.annotation.RequiresApi;
import android.util.Log;
import androidx.annotation.RequiresApi;
class AndroidStandardFormatStrategy implements MediaFormatStrategy {
public static final int AUDIO_BITRATE_AS_IS = -1;
public static final int AUDIO_CHANNELS_AS_IS = -1;

View file

@ -1 +0,0 @@
/build

View file

@ -1,27 +0,0 @@
apply plugin: 'com.android.library'
android {
lintOptions {
abortOnError false
}
compileSdkVersion 28
defaultConfig {
minSdkVersion 14
targetSdkVersion 28
versionCode 11
versionName "1.1.3"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
}
//apply from: '../maven_push.gradle'

View file

@ -1,3 +0,0 @@
POM_NAME=FullscreenVideoView Library
POM_ARTIFACT_ID=fullscreenvideoview
POM_PACKAGING=aar

View file

@ -1,17 +0,0 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Applications/ADT/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

View file

@ -1,4 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.github.rtoshiro.view.video">
</manifest>

View file

@ -1,336 +0,0 @@
/**
* Copyright (C) 2016 Toshiro Sugii
* <p/>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p/>
* http://www.apache.org/licenses/LICENSE-2.0
* <p/>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.github.rtoshiro.view.video;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.media.MediaPlayer;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageButton;
import android.widget.RelativeLayout;
import android.widget.SeekBar;
import android.widget.TextView;
import java.util.Locale;
public class FullscreenVideoLayout extends FullscreenVideoView implements View.OnClickListener, SeekBar.OnSeekBarChangeListener, MediaPlayer.OnPreparedListener {
/**
* Log cat TAG name
*/
private final static String TAG = "FullscreenVideoLayout";
/**
* RelativeLayout that contains all control related views
*/
public View videoControlsView;
/**
* SeekBar reference (from videoControlsView)
*/
protected SeekBar seekBar;
/**
* Reference to ImageButton play
*/
protected ImageButton imgplay;
/**
* Reference to ImageButton fullscreen
*/
protected ImageButton imgfullscreen;
/**
* Reference to TextView for elapsed time and total time
*/
protected TextView textTotal, textElapsed;
/**
* Handler and Runnable to keep tracking on elapsed time
*/
protected static final Handler TIME_THREAD = new Handler();
protected Runnable updateTimeRunnable = new Runnable() {
public void run() {
updateCounter();
TIME_THREAD.postDelayed(this, 200);
}
};
public FullscreenVideoLayout(Context context) {
super(context);
}
public FullscreenVideoLayout(Context context, AttributeSet attrs) {
super(context, attrs);
}
public FullscreenVideoLayout(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
protected void release() {
super.release();
}
@Override
protected void initObjects() {
super.initObjects();
if (this.videoControlsView == null) {
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
this.videoControlsView = inflater.inflate(R.layout.view_videocontrols, this, false);
}
if (videoControlsView != null) {
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.FILL_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT);
params.addRule(ALIGN_PARENT_BOTTOM);
addView(videoControlsView, params);
this.seekBar = this.videoControlsView.findViewById(R.id.vcv_seekbar);
this.imgfullscreen = this.videoControlsView.findViewById(R.id.vcv_img_fullscreen);
this.imgplay = this.videoControlsView.findViewById(R.id.vcv_img_play);
this.textTotal = this.videoControlsView.findViewById(R.id.vcv_txt_total);
this.textElapsed = this.videoControlsView.findViewById(R.id.vcv_txt_elapsed);
}
if (this.imgplay != null)
this.imgplay.setOnClickListener(this);
if (this.imgfullscreen != null)
this.imgfullscreen.setOnClickListener(this);
if (this.seekBar != null)
this.seekBar.setOnSeekBarChangeListener(this);
// Start controls invisible. Make it visible when it is prepared
if (this.videoControlsView != null)
this.videoControlsView.setVisibility(View.INVISIBLE);
}
@Override
protected void releaseObjects() {
super.releaseObjects();
if (this.videoControlsView != null)
removeView(this.videoControlsView);
}
protected void startCounter() {
Log.d(TAG, "startCounter");
TIME_THREAD.postDelayed(updateTimeRunnable, 200);
}
protected void stopCounter() {
Log.d(TAG, "stopCounter");
TIME_THREAD.removeCallbacks(updateTimeRunnable);
}
protected void updateCounter() {
if (this.textElapsed == null)
return;
int elapsed = getCurrentPosition();
// getCurrentPosition is a little bit buggy :(
if (elapsed > 0 && elapsed < getDuration()) {
seekBar.setProgress(elapsed);
elapsed = Math.round(elapsed / 1000.f);
long s = elapsed % 60;
long m = (elapsed / 60) % 60;
long h = (elapsed / (60 * 60)) % 24;
if (h > 0)
textElapsed.setText(String.format(Locale.US, "%d:%02d:%02d", h, m, s));
else
textElapsed.setText(String.format(Locale.US, "%02d:%02d", m, s));
}
}
@Override
public void onCompletion(MediaPlayer mp) {
Log.d(TAG, "onCompletion");
super.onCompletion(mp);
stopCounter();
updateControls();
if (currentState != State.ERROR)
updateCounter();
}
@Override
public boolean onError(MediaPlayer mp, int what, int extra) {
boolean result = super.onError(mp, what, extra);
stopCounter();
updateControls();
return result;
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
if (getCurrentState() == State.END) {
Log.d(TAG, "onDetachedFromWindow END");
stopCounter();
}
}
@Override
protected void tryToPrepare() {
Log.d(TAG, "tryToPrepare");
super.tryToPrepare();
if (getCurrentState() == State.PREPARED || getCurrentState() == State.STARTED) {
if (textElapsed != null && textTotal != null) {
int total = getDuration();
if (total > 0) {
seekBar.setMax(total);
seekBar.setProgress(0);
total = total / 1000;
long s = total % 60;
long m = (total / 60) % 60;
long h = (total / (60 * 60)) % 24;
if (h > 0) {
textElapsed.setText("00:00:00");
textTotal.setText(String.format(Locale.US, "%d:%02d:%02d", h, m, s));
} else {
textElapsed.setText("00:00");
textTotal.setText(String.format(Locale.US, "%02d:%02d", m, s));
}
}
}
if (videoControlsView != null)
videoControlsView.setVisibility(View.VISIBLE);
}
}
@Override
public void start() throws IllegalStateException {
Log.d(TAG, "start");
if (!isPlaying()) {
super.start();
startCounter();
updateControls();
}
}
@Override
public void pause() throws IllegalStateException {
Log.d(TAG, "pause");
if (isPlaying()) {
stopCounter();
super.pause();
updateControls();
}
}
@Override
public void reset() {
Log.d(TAG, "reset");
super.reset();
stopCounter();
updateControls();
}
@Override
public void stop() throws IllegalStateException {
Log.d(TAG, "stop");
super.stop();
stopCounter();
updateControls();
}
protected void updateControls() {
if (imgplay == null) return;
Drawable icon;
if (getCurrentState() == State.STARTED) {
icon = context.getResources().getDrawable(R.drawable.fvl_selector_pause);
} else {
icon = context.getResources().getDrawable(R.drawable.fvl_selector_play);
}
imgplay.setBackgroundDrawable(icon);
}
public void hideControls() {
Log.d(TAG, "hideControls");
if (videoControlsView != null) {
videoControlsView.setVisibility(View.INVISIBLE);
}
}
public void showControls() {
Log.d(TAG, "showControls");
if (videoControlsView != null) {
videoControlsView.setVisibility(View.VISIBLE);
}
}
/**
* Onclick action
* Controls play button and fullscreen button.
*
* @param v View defined in XML
*/
@Override
public void onClick(View v) {
if (v.getId() == R.id.vcv_img_play) {
if (isPlaying()) {
pause();
} else {
start();
}
} else {
setFullscreen(!isFullscreen());
}
}
/**
* SeekBar Listener
*/
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
Log.d(TAG, "onProgressChanged " + progress);
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
stopCounter();
Log.d(TAG, "onStartTrackingTouch");
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
int progress = seekBar.getProgress();
seekTo(progress);
Log.d(TAG, "onStopTrackingTouch");
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 153 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 154 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -1,27 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2010 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background" android:drawable="@drawable/fvl_track" />
<item android:id="@android:id/secondaryProgress">
<scale android:scaleWidth="100%"
android:drawable="@drawable/fvl_secondary" />
</item>
<item android:id="@android:id/progress">
<scale android:scaleWidth="100%"
android:drawable="@drawable/fvl_primary" />
</item>
</layer-list>

View file

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/fvl_fullscreen_reader" android:state_pressed="false" />
<item android:drawable="@drawable/fvl_fullscreen_reader_white" android:state_pressed="true" />
</selector>

View file

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/fvl_pause_reader" android:state_pressed="false" />
<item android:drawable="@drawable/fvl_pause_reader_white" android:state_pressed="true" />
</selector>

View file

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/fvl_play_reader" android:state_pressed="false" />
<item android:drawable="@drawable/fvl_play_reader_white" android:state_pressed="true" />
</selector>

View file

@ -1,82 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
android:id="@+id/rel_videocontrols"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="0dp"
android:background="#80cccccc">
<ImageButton
android:id="@+id/vcv_img_play"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:layout_marginStart="0dp"
android:layout_marginLeft="0dp"
android:layout_marginTop="0dp"
android:layout_marginEnd="0dp"
android:layout_marginRight="0dp"
android:layout_marginBottom="0dp"
android:background="@drawable/fvl_selector_play" />
<TextView
android:id="@+id/vcv_txt_elapsed"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toRightOf="@+id/vcv_img_play"
android:text="00:00"
android:textColor="@android:color/black" />
<ImageButton
android:id="@+id/vcv_img_fullscreen"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"
android:layout_marginStart="0dp"
android:layout_marginLeft="0dp"
android:layout_marginTop="0dp"
android:layout_marginEnd="0dp"
android:layout_marginRight="0dp"
android:layout_marginBottom="0dp"
android:background="@drawable/fvl_selector_fullscreen" />
<TextView
android:id="@+id/vcv_txt_total"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_marginRight="5dp"
android:layout_toLeftOf="@+id/vcv_img_fullscreen"
android:text="00:00"
android:textColor="@android:color/black" />
<SeekBar
android:id="@+id/vcv_seekbar"
style="@android:style/Widget.ProgressBar.Horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_margin="2dp"
android:layout_marginStart="5dp"
android:layout_marginLeft="5dp"
android:layout_marginTop="5dp"
android:layout_marginEnd="5dp"
android:layout_marginRight="5dp"
android:layout_marginBottom="5dp"
android:layout_toLeftOf="@+id/vcv_txt_total"
android:layout_toRightOf="@+id/vcv_txt_elapsed"
android:indeterminateDrawable="@drawable/fvl_progress"
android:maxHeight="13dp"
android:minHeight="13dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:progressDrawable="@drawable/fvl_progress"
android:thumb="@drawable/fvl_control_normal"
android:thumbOffset="16dp" />
</RelativeLayout>

View file

@ -1,6 +0,0 @@
<resources>
<string name="app_name">VideoLayout</string>
<string name="hello_world">Hello world!</string>
<string name="action_settings">Settings</string>
</resources>

View file

@ -7,7 +7,7 @@ repositories {
}
dependencies {
implementation 'rocks.xmpp:precis:1.0.0'
implementation 'rocks.xmpp:precis:1.1.0'
}
sourceCompatibility = "8"

40
proguard-rules.pro vendored
View file

@ -12,6 +12,8 @@
**[] $VALUES;
public *;
}
-keep class com.squareup.okhttp.** { *; }
-keep interface com.squareup.okhttp.** { *; }
-dontwarn org.bouncycastle.mail.**
-dontwarn org.bouncycastle.x509.util.LDAPStoreHelper
@ -22,6 +24,10 @@
-dontwarn java.lang.**
-dontwarn javax.lang.**
-keepclassmembers class de.pixart.messenger.http.services.** {
!transient <fields>;
}
# JSR 305 annotations are for embedding nullability information.
-dontwarn javax.annotation.**
@ -32,4 +38,36 @@
-dontwarn org.codehaus.mojo.animal_sniffer.*
# OkHttp platform used only on JVM and when Conscrypt dependency is available.
-dontwarn okhttp3.internal.platform.ConscryptPlatform
-dontwarn okhttp3.internal.platform.ConscryptPlatform
# Retrofit does reflection on generic parameters. InnerClasses is required to use Signature and
# EnclosingMethod is required to use InnerClasses.
-keepattributes Signature, InnerClasses, EnclosingMethod
# Retrofit does reflection on method and parameter annotations.
-keepattributes RuntimeVisibleAnnotations, RuntimeVisibleParameterAnnotations
# Retain service method parameters when optimizing.
-keepclassmembers,allowshrinking,allowobfuscation interface * {
@retrofit2.http.* <methods>;
}
# Ignore annotation used for build tooling.
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
# Ignore JSR 305 annotations for embedding nullability information.
-dontwarn javax.annotation.**
# Guarded by a NoClassDefFoundError try/catch and only used when on the classpath.
-dontwarn kotlin.Unit
# Top-level functions that can only be used by Kotlin.
-dontwarn retrofit2.KotlinExtensions
-dontwarn retrofit2.KotlinExtensions$*
# With R8 full mode, it sees no subtypes of Retrofit interfaces since they are created with a Proxy
# and replaces all potential values with null. Explicitly keeping the interfaces prevents this.
-if interface * { @retrofit2.http.* <methods>; }
-keep,allowobfuscation interface <1>

View file

@ -1,4 +1,3 @@
include ':libs:android-transcoder'
include ':libs:xmpp-addr'
include ':libs:fullscreenvideoview'
rootProject.name = 'PixArtMessenger'

View file

@ -0,0 +1,26 @@
package de.pixart.messenger.services;
import android.content.Context;
import android.os.Build;
import androidx.emoji.bundled.BundledEmojiCompatConfig;
import androidx.emoji.text.EmojiCompat;
public class EmojiService {
private final Context context;
public EmojiService(Context context) {
this.context = context;
}
public void init(boolean useBundledEmoji) {
BundledEmojiCompatConfig config = new BundledEmojiCompatConfig(context);
//On recent Androids we assume to have the latest emojis
//there are some annoying bugs with emoji compat that make it a safer choice not to use it when possible
// a) the text preview has annoying glitches when the cut of text contains emojis (the emoji will be half visible)
// b) can trigger a hardware rendering bug https://issuetracker.google.com/issues/67102093
config.setReplaceAll(useBundledEmoji && Build.VERSION.SDK_INT < Build.VERSION_CODES.O);
EmojiCompat.init(config);
}
}

View file

@ -1,6 +1,7 @@
package de.pixart.messenger.services;
import de.pixart.messenger.entities.Account;
import de.pixart.messenger.entities.Conversation;
public class PushManagementService {
@ -10,7 +11,19 @@ public class PushManagementService {
this.mXmppConnectionService = service;
}
public void registerPushTokenOnServer(Account account) {
void registerPushTokenOnServer(Account account) {
//stub implementation. only affects playstore flavor
}
void registerPushTokenOnServer(Conversation conversation) {
//stub implementation. only affects playstore flavor
}
void unregisterChannel(Account account, String hash) {
//stub implementation. only affects playstore flavor
}
void disablePushOnServer(Conversation conversation) {
//stub implementation. only affects playstore flavor
}

View file

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

View file

@ -0,0 +1,13 @@
package de.pixart.messenger.utils;
import de.pixart.messenger.ui.MagicCreateActivity;
import de.pixart.messenger.ui.WelcomeActivity;
public class InstallReferrerUtils {
public InstallReferrerUtils(WelcomeActivity welcomeActivity) {
}
public static void markInstallReferrerExecuted(MagicCreateActivity magicCreateActivity) {
}
}

View file

@ -22,6 +22,7 @@
<uses-permission android:name="com.android.alarm.permission.SET_ALARM" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="com.google.android.apps.photos.permission.GOOGLE_PHOTOS" />
<uses-permission
android:name="android.permission.READ_PHONE_STATE"
tools:node="remove" />
@ -48,18 +49,19 @@
android:required="false" />
<application
android:name="android.support.multidex.MultiDexApplication"
android:name="androidx.multidex.MultiDexApplication"
android:allowBackup="false"
android:appCategory="social"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:largeHeap="true"
android:networkSecurityConfig="@xml/network_security_configuration"
android:requestLegacyExternalStorage="true"
android:supportsRtl="true"
android:theme="@style/ConversationsTheme"
tools:ignore="GoogleAppIndexingWarning"
tools:replace="android:label, android:allowBackup"
tools:targetApi="o">
tools:targetApi="q">
<meta-data
android:name="com.google.android.gms.car.application"
@ -165,7 +167,22 @@
<activity
android:name=".ui.ImportBackupActivity"
android:label="@string/restore_backup"
android:launchMode="singleTask" />
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/vnd.conversations.backup" />
<data android:scheme="content" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="application/vnd.conversations.backup" />
<data android:scheme="file" />
</intent-filter>
</activity>
<activity
android:name=".ui.SettingsActivity"
android:label="@string/title_activity_settings">
@ -212,7 +229,7 @@
android:windowSoftInputMode="stateHidden" />
<activity
android:name=".ui.ContactDetailsActivity"
android:label="@string/title_activity_contact_details"
android:label="@string/contact_details"
android:windowSoftInputMode="stateHidden" />
<activity
android:name=".ui.PublishProfilePictureActivity"
@ -251,19 +268,24 @@
<activity
android:name=".ui.RecordingActivity"
android:configChanges="orientation|screenSize"
android:launchMode="singleTask"
android:theme="@style/ConversationsTheme.Dialog" />
<activity
android:name=".ui.ShareLocationActivity"
android:label="@string/share_location" />
android:label="@string/share_location"
android:launchMode="singleTask" />
<activity
android:name=".ui.ShowLocationActivity"
android:label="@string/show_location"></activity>
android:label="@string/show_location"
android:launchMode="singleTask" />
<activity
android:name=".ui.SearchActivity"
android:label="@string/search_messages" />
android:label="@string/search_messages"
android:launchMode="singleTask" />
<activity
android:name=".ui.MediaViewerActivity"
android:configChanges="orientation|screenSize"
android:launchMode="singleTask"
android:theme="@style/ConversationsTheme.FullScreen" />
<activity
android:name=".ui.TrustKeysActivity"
@ -272,14 +294,15 @@
<activity
android:name=".ui.AboutActivity"
android:label="@string/title_activity_about"
android:launchMode="singleTask"
android:parentActivityName=".ui.SettingsActivity">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".ui.SettingsActivity" />
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.PREFERENCE" />
</intent-filter>
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".ui.SettingsActivity" />
</activity>
<activity
android:name=".ui.UpdaterActivity"
@ -289,18 +312,21 @@
android:theme="@style/ConversationsTheme" />
<activity
android:name=".ui.ShortcutActivity"
android:label="@string/contact">
android:label="@string/contact"
android:launchMode="singleTask">
<intent-filter>
<action android:name="android.intent.action.CREATE_SHORTCUT" />
</intent-filter>
</activity>
<activity
android:name="com.theartofdev.edmodo.cropper.CropImageActivity"
android:launchMode="singleTask"
android:theme="@style/Base.Theme.AppCompat" />
<activity android:name=".ui.MemorizingActivity" />
<activity
android:name=".ui.MediaBrowserActivity"
android:label="@string/media_browser" />
android:label="@string/media_browser"
android:launchMode="singleTask" />
<activity
android:name=".ui.EnterNameActivity"
android:label="@string/enter_your_name"
@ -311,10 +337,15 @@
android:launchMode="singleTask" />
<activity
android:name=".ui.MucUsersActivity"
android:label="@string/group_chat_members" />
android:label="@string/group_chat_members"
android:launchMode="singleTask" />
<activity
android:name=".ui.ChannelDiscoveryActivity"
android:label="@string/discover_channels" />
android:label="@string/discover_channels"
android:launchMode="singleTask" />
<activity android:name=".ui.IntroActivity"
android:launchMode="singleTask"
android:label="@string/app_name" />
<service android:name=".services.ExportBackupService" />
<service android:name=".services.ImportBackupService" />
@ -327,7 +358,7 @@
</service>
<provider
android:name="android.support.v4.content.FileProvider"
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.files"
android:exported="false"
android:grantUriPermissions="true">

File diff suppressed because one or more lines are too long

View file

@ -25,6 +25,10 @@
user-select: none;
-webkit-user-drag: none;
}
/* Prevents IE11 from highlighting tiles in blue */
.leaflet-tile::selection {
background: transparent;
}
/* Safari renders non-retina tile on retina better with this, but Chrome is worse */
.leaflet-safari .leaflet-tile {
image-rendering: -webkit-optimize-contrast;
@ -237,7 +241,8 @@
.leaflet-marker-icon.leaflet-interactive,
.leaflet-image-layer.leaflet-interactive,
.leaflet-pane > svg path.leaflet-interactive {
.leaflet-pane > svg path.leaflet-interactive,
svg.leaflet-image-layer.leaflet-interactive path {
pointer-events: visiblePainted; /* IE 9-10 doesn't have auto */
pointer-events: auto;
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -13,15 +13,16 @@
</head>
<body>
<style>
#map{width:100%;height:100%;position:fixed;top:0px;left:0px;right:0px;bottom:0px}
</style>
<div id='map'>
</div>
<div id='map'></div>
<script type="text/javascript">
var marker;
//get location from URL
var getUrlParameter = function getUrlParameter(sParam) {
var sPageURL = decodeURIComponent(window.location.search.substring(1)),
sURLVariables = sPageURL.split('&'),
@ -30,24 +31,33 @@
for (i = 0; i < sURLVariables.length; i++) {
sParameterName = sURLVariables[i].split('=');
if (sParameterName[0] === sParam) {
return sParameterName[1] === undefined ? true : sParameterName[1];
}
}
};
if (typeof getUrlParameter('lat') === 'undefined' && typeof getUrlParameter('lon') === 'undefined') {
var map = L.map('map', {
zoomControl: true,
attributionControl: false
}).fitWorld();
} else {
// create map
if (getUrlParameter('lat') != null && getUrlParameter('lon') != null) {
var map = L.map('map', {
zoomControl: true,
attributionControl: true
}).setView([getUrlParameter('lat'), getUrlParameter('lon')], 15);
// add marker
if (!marker) {
marker = L.marker([getUrlParameter('lat'), getUrlParameter('lon')]).addTo(map);
marker.bindPopup((getUrlParameter('name')), {
closeOnClick: false,
closeButton: false,
autoClose: false
}).openPopup();
}
} else {
var map = L.map('map', {
zoomControl: true,
attributionControl: true
}).fitWorld();
}
map.addLayer(new L.TileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png", {
@ -55,16 +65,20 @@
maxZoom: 19
}));
if (typeof getUrlParameter('lat') !== 'undefined' && typeof getUrlParameter('lon') !== 'undefined' && typeof getUrlParameter('name') !== 'undefined') {
var marker = L.marker([getUrlParameter('lat'), getUrlParameter('lon')]).addTo(map);
marker.bindPopup(getUrlParameter('name'), {
closeOnClick: false,
closeButton: false,
autoClose: false
}).openPopup();
} else if (typeof getUrlParameter('lat') !== 'undefined' && typeof getUrlParameter('lon') !== 'undefined' && typeof getUrlParameter('name') === 'undefined') {
var marker = L.marker([getUrlParameter('lat'), getUrlParameter('lon')]).addTo(map);
// Change view to coordinates
function toCoordinates(latIntent, lonIntent, Name){
map.setView([parseFloat(latIntent), parseFloat(lonIntent)], 15);
if (!marker) {
marker = L.marker([parseFloat(latIntent), parseFloat(lonIntent)]).addTo(map);
marker.bindPopup(("..."), {
closeOnClick: false,
closeButton: false,
autoClose: false
}).openPopup();
}
marker.setLatLng([parseFloat(latIntent), parseFloat(lonIntent)]).update();
marker.setPopupContent(Name);
}
</script>
</body>
</body>
</html>

View file

@ -39,8 +39,7 @@ public final class Config {
return (ENCRYPTION_MASK & (ENCRYPTION_MASK - 1)) != 0;
}
public static final String LOGTAG = "Pix-Art_Messenger";
public static final String LOGTAG = BuildConfig.LOGTAG;
public static final Jid BUG_REPORTS = Jid.of("bugs@pix-art.de");
@ -51,11 +50,13 @@ public final class Config {
public static final String XMPP_IP = null; //BuildConfig.XMPP_IP; // set to null means disable
public static final Integer[] XMPP_Ports = null; //BuildConfig.XMPP_Ports; // set to null means disable
public static final String DOMAIN_LOCK = null; //BuildConfig.DOMAIN_LOCK; //only allow account creation for this domain
public static final String MAGIC_CREATE_DOMAIN = "blabber.im";
public static final String DOMAIN_LOCK = BuildConfig.DOMAIN_LOCK; //only allow account creation for this domain
public static final String MAGIC_CREATE_DOMAIN = BuildConfig.MAGIC_CREATE_DOMAIN; //"blabber.im";
public static final String QUICKSY_DOMAIN = "quicksy.im";
public static final String CHANNEL_DISCOVERY = "https://search.jabbercat.org";
public static final String CHANNEL_DISCOVERY = "https://search.jabber.network";
public static final String DEFAULT_INVIDIOUS_HOST = "invidio.us";
public static final boolean DISALLOW_REGISTRATION_IN_UI = false; //hide the register checkbox
public static final boolean SHOW_INTRO = BuildConfig.SHOW_INTRO;
public static final boolean USE_RANDOM_RESOURCE_ON_EVERY_BIND = false;
@ -109,15 +110,18 @@ public final class Config {
public static final boolean REMOVE_BROKEN_DEVICES = false;
public static final boolean OMEMO_PADDING = false;
public static final boolean PUT_AUTH_TAG_INTO_KEY = true;
public static final boolean TWELVE_BYTE_IV = true;
public static final int MAX_DISPLAY_MESSAGE_CHARS = 4096;
public static final int MAX_STORAGE_MESSAGE_CHARS = 1024 * 1024 * 1024;
public static final int MAX_STORAGE_MESSAGE_CHARS = 2 * 1024 * 1024; //2MB
public static final boolean ExportLogs = true; // automatically export logs
public static final int ExportLogs_Hour = 4; //Time - hours: valid values from 0 to 23
public static final int ExportLogs_Minute = 0; //Time - minutes: valid values from 0 to 59
public static final boolean USE_BOOKMARKS2 = false;
public static final boolean DISABLE_PROXY_LOOKUP = false; //useful to debug ibb
public static final boolean USE_DIRECT_JINGLE_CANDIDATES = true;
public static final boolean DISABLE_HTTP_UPLOAD = false;
public static final boolean EXTENDED_SM_LOGGING = false; // log stanza counts
public static final boolean BACKGROUND_STANZA_LOGGING = false; //log all stanzas that were received while the app is in background
@ -130,11 +134,14 @@ public final class Config {
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 MUC_LEAVE_BEFORE_JOIN = true;
public static final long MAM_MAX_CATCHUP = MILLISECONDS_IN_DAY * 5;
public static final boolean USE_LMC_VERSION_1_1 = true;
public static final long MAM_MAX_CATCHUP = MILLISECONDS_IN_DAY * 30;
public static final int MAM_MAX_MESSAGES = 750;
public static final ChatState DEFAULT_CHATSTATE = ChatState.ACTIVE;
public static final ChatState DEFAULT_CHAT_STATE = ChatState.ACTIVE;
public static final int TYPING_TIMEOUT = 5;
public static final int EXPIRY_INTERVAL = 30 * 60 * 1000; // 30 minutes

View file

@ -23,7 +23,9 @@ public class JabberIdContact extends AbstractPhoneContact {
super(cursor);
try {
this.jid = Jid.of(cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Im.DATA)));
} catch (IllegalArgumentException | NullPointerException e) {
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException(e);
} catch (NullPointerException e) {
throw new IllegalArgumentException(e);
}
}

View file

@ -39,12 +39,17 @@ import de.pixart.messenger.ui.SettingsActivity;
public class OmemoSetting {
private static boolean always = false;
private static boolean never = false;
private static int encryption = Message.ENCRYPTION_AXOLOTL;
public static boolean isAlways() {
return always;
}
public static boolean isNever() {
return never;
}
public static int getEncryption() {
return encryption;
}
@ -54,14 +59,22 @@ public class OmemoSetting {
switch (value) {
case "always":
always = true;
never = false;
encryption = Message.ENCRYPTION_AXOLOTL;
break;
case "default_on":
always = false;
never = false;
encryption = Message.ENCRYPTION_AXOLOTL;
break;
case "always_off":
always = false;
never = true;
encryption = Message.ENCRYPTION_NONE;
break;
default:
always = false;
never = false;
encryption = Message.ENCRYPTION_NONE;
break;

View file

@ -95,7 +95,9 @@ public class OtrService extends OtrCryptoEngineImpl implements OtrEngineHost {
this.account.setKey("otr_p", privateKeySpec.getP().toString(16));
this.account.setKey("otr_q", privateKeySpec.getQ().toString(16));
this.account.setKey("otr_y", publicKeySpec.getY().toString(16));
} catch (final NoSuchAlgorithmException | InvalidKeySpecException e) {
} catch (final NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (final InvalidKeySpecException e) {
e.printStackTrace();
}
@ -186,7 +188,7 @@ public class OtrService extends OtrCryptoEngineImpl implements OtrEngineHost {
try {
Jid jid = OtrJidHelper.fromSessionID(session);
Conversation conversation = mXmppConnectionService.find(account, jid);
if (conversation != null && conversation.setOutgoingChatState(Config.DEFAULT_CHATSTATE)) {
if (conversation != null && conversation.setOutgoingChatState(Config.DEFAULT_CHAT_STATE)) {
if (mXmppConnectionService.sendChatStates()) {
packet.addChild(ChatState.toElement(conversation.getOutgoingChatState()));
}

View file

@ -201,6 +201,9 @@ public class PgpDecryptionService {
if (fixedFile.getParentFile().mkdirs()) {
Log.d(Config.LOGTAG, "created parent directories for " + fixedFile.getAbsolutePath());
}
synchronized (mXmppConnectionService.FILENAMES_TO_IGNORE_DELETION) {
mXmppConnectionService.FILENAMES_TO_IGNORE_DELETION.add(outputFile.getAbsolutePath());
}
if (outputFile.renameTo(fixedFile)) {
Log.d(Config.LOGTAG, "renamed " + outputFile.getAbsolutePath() + " to " + fixedFile.getAbsolutePath());
message.setRelativeFilePath(path);

View file

@ -2,9 +2,10 @@ package de.pixart.messenger.crypto;
import android.app.PendingIntent;
import android.content.Intent;
import android.support.annotation.StringRes;
import android.util.Log;
import androidx.annotation.StringRes;
import org.openintents.openpgp.OpenPgpError;
import org.openintents.openpgp.OpenPgpSignatureResult;
import org.openintents.openpgp.util.OpenPgpApi;
@ -92,7 +93,7 @@ public class PgpEngine {
}
break;
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
callback.userInputRequried(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), message);
callback.userInputRequired(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), message);
break;
case OpenPgpApi.RESULT_CODE_ERROR:
OpenPgpError error = result.getParcelableExtra(OpenPgpApi.RESULT_ERROR);
@ -131,7 +132,7 @@ public class PgpEngine {
callback.success(message);
break;
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
callback.userInputRequried(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), message);
callback.userInputRequired(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), message);
break;
case OpenPgpApi.RESULT_CODE_ERROR:
logError(conversation.getAccount(), result.getParcelableExtra(OpenPgpApi.RESULT_ERROR));
@ -198,7 +199,7 @@ public class PgpEngine {
callback.success(account);
return;
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
callback.userInputRequried(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), account);
callback.userInputRequired(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), account);
return;
case OpenPgpApi.RESULT_CODE_ERROR:
logError(account, result.getParcelableExtra(OpenPgpApi.RESULT_ERROR));
@ -247,7 +248,7 @@ public class PgpEngine {
callback.success(signatureBuilder.toString());
return;
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
callback.userInputRequried(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), status);
callback.userInputRequired(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), status);
return;
case OpenPgpApi.RESULT_CODE_ERROR:
OpenPgpError error = result.getParcelableExtra(OpenPgpApi.RESULT_ERROR);
@ -274,7 +275,7 @@ public class PgpEngine {
callback.success(contact);
return;
case OpenPgpApi.RESULT_CODE_USER_INTERACTION_REQUIRED:
callback.userInputRequried(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), contact);
callback.userInputRequired(result.getParcelableExtra(OpenPgpApi.RESULT_INTENT), contact);
return;
case OpenPgpApi.RESULT_CODE_ERROR:
logError(contact.getAccount(), result.getParcelableExtra(OpenPgpApi.RESULT_ERROR));

View file

@ -22,6 +22,7 @@ import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import javax.net.ssl.SSLSession;
@ -80,14 +81,14 @@ public class XmppDomainVerifier implements DomainHostnameVerifier {
break;
}
Log.d(LOGTAG, "comparing " + needle.substring(i) + " and " + entry.substring(1));
if (needle.substring(i).equals(entry.substring(1))) {
if (needle.substring(i).equalsIgnoreCase(entry.substring(1))) {
Log.d(LOGTAG, "domain " + needle + " matched " + entry);
return true;
}
offset = i + 1;
}
} else {
if (entry.equals(needle)) {
if (entry.equalsIgnoreCase(needle)) {
Log.d(LOGTAG, "domain " + needle + " matched " + entry);
return true;
}
@ -117,25 +118,25 @@ public class XmppDomainVerifier implements DomainHostnameVerifier {
List<String> domains = new ArrayList<>();
if (alternativeNames != null) {
for (List<?> san : alternativeNames) {
Integer type = (Integer) san.get(0);
final Integer type = (Integer) san.get(0);
if (type == 0) {
Pair<String, String> otherName = parseOtherName((byte[]) san.get(1));
if (otherName != null) {
final Pair<String, String> otherName = parseOtherName((byte[]) san.get(1));
if (otherName != null && otherName.first != null && otherName.second != null) {
switch (otherName.first) {
case SRV_NAME:
srvNames.add(otherName.second);
srvNames.add(otherName.second.toLowerCase(Locale.US));
break;
case XMPP_ADDR:
xmppAddrs.add(otherName.second);
xmppAddrs.add(otherName.second.toLowerCase(Locale.US));
break;
default:
Log.d(LOGTAG, "oid: " + otherName.first + " value: " + otherName.second);
}
}
} else if (type == 2) {
Object value = san.get(1);
final Object value = san.get(1);
if (value instanceof String) {
domains.add((String) value);
domains.add(((String) value).toLowerCase(Locale.US));
}
}
}

View file

@ -2,11 +2,12 @@ package de.pixart.messenger.crypto.axolotl;
import android.os.Bundle;
import android.security.KeyChain;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import android.util.Pair;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.whispersystems.libsignal.IdentityKey;
import org.whispersystems.libsignal.IdentityKeyPair;
@ -66,8 +67,8 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
public static final String LOGPREFIX = "AxolotlService";
public static final int NUM_KEYS_TO_PUBLISH = 100;
public static final int publishTriesThreshold = 3;
private static final int NUM_KEYS_TO_PUBLISH = 100;
private static final int publishTriesThreshold = 3;
private final Account account;
private final XmppConnectionService mXmppConnectionService;
@ -79,16 +80,37 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
private final Map<Jid, Boolean> fetchDeviceListStatus = new HashMap<>();
private final HashMap<Jid, List<OnDeviceIdsFetched>> fetchDeviceIdsMap = new HashMap<>();
private final SerialSingleThreadExecutor executor;
private final Set<SignalProtocolAddress> healingAttempts = new HashSet<>();
private final HashSet<Integer> cleanedOwnDeviceIds = new HashSet<>();
private final Set<Integer> PREVIOUSLY_REMOVED_FROM_ANNOUNCEMENT = new HashSet<>();
private int numPublishTriesOnEmptyPep = 0;
private boolean pepBroken = false;
private final Set<SignalProtocolAddress> healingAttempts = new HashSet<>();
private int lastDeviceListNotificationHash = 0;
private final HashSet<Integer> cleanedOwnDeviceIds = new HashSet<>();
private 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 AtomicBoolean changeAccessMode = new AtomicBoolean(false);
public AxolotlService(Account account, XmppConnectionService connectionService) {
if (account == null || connectionService == null) {
throw new IllegalArgumentException("account and service cannot be null");
}
if (Security.getProvider("BC") == null) {
Security.addProvider(new BouncyCastleProvider());
}
this.mXmppConnectionService = connectionService;
this.account = account;
this.axolotlStore = new SQLiteAxolotlStore(this.account, this.mXmppConnectionService);
this.deviceIds = new HashMap<>();
this.messageCache = new HashMap<>();
this.sessions = new SessionMap(mXmppConnectionService, axolotlStore, account);
this.fetchStatusMap = new FetchStatusMap();
this.executor = new SerialSingleThreadExecutor("Axolotl");
}
public static String getLogprefix(Account account) {
return LOGPREFIX + " (" + account.getJid().asBareJid().toString() + "): ";
}
@Override
public void onAdvancedStreamFeaturesAvailable(Account account) {
if (Config.supportOmemo()
@ -145,172 +167,6 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
return false;
}
private static class AxolotlAddressMap<T> {
protected Map<String, Map<Integer, T>> map;
protected final Object MAP_LOCK = new Object();
public AxolotlAddressMap() {
this.map = new HashMap<>();
}
public void put(SignalProtocolAddress address, T value) {
synchronized (MAP_LOCK) {
Map<Integer, T> devices = map.get(address.getName());
if (devices == null) {
devices = new HashMap<>();
map.put(address.getName(), devices);
}
devices.put(address.getDeviceId(), value);
}
}
public T get(SignalProtocolAddress address) {
synchronized (MAP_LOCK) {
Map<Integer, T> devices = map.get(address.getName());
if (devices == null) {
return null;
}
return devices.get(address.getDeviceId());
}
}
public Map<Integer, T> getAll(String name) {
synchronized (MAP_LOCK) {
Map<Integer, T> devices = map.get(name);
if (devices == null) {
return new HashMap<>();
}
return devices;
}
}
public boolean hasAny(SignalProtocolAddress address) {
synchronized (MAP_LOCK) {
Map<Integer, T> devices = map.get(address.getName());
return devices != null && !devices.isEmpty();
}
}
public void clear() {
map.clear();
}
}
private static class SessionMap extends AxolotlAddressMap<XmppAxolotlSession> {
private final XmppConnectionService xmppConnectionService;
private final Account account;
public SessionMap(XmppConnectionService service, SQLiteAxolotlStore store, Account account) {
super();
this.xmppConnectionService = service;
this.account = account;
this.fillMap(store);
}
public Set<Jid> findCounterpartsForSourceId(Integer sid) {
Set<Jid> candidates = new HashSet<>();
synchronized (MAP_LOCK) {
for (Map.Entry<String, Map<Integer, XmppAxolotlSession>> entry : map.entrySet()) {
String key = entry.getKey();
if (entry.getValue().containsKey(sid)) {
candidates.add(Jid.of(key));
}
}
}
return candidates;
}
private void putDevicesForJid(String bareJid, List<Integer> deviceIds, SQLiteAxolotlStore store) {
for (Integer deviceId : deviceIds) {
SignalProtocolAddress axolotlAddress = new SignalProtocolAddress(bareJid, deviceId);
IdentityKey identityKey = store.loadSession(axolotlAddress).getSessionState().getRemoteIdentityKey();
if (Config.X509_VERIFICATION) {
X509Certificate certificate = store.getFingerprintCertificate(CryptoHelper.bytesToHex(identityKey.getPublicKey().serialize()));
if (certificate != null) {
Bundle information = CryptoHelper.extractCertificateInformation(certificate);
try {
final String cn = information.getString("subject_cn");
final Jid jid = Jid.of(bareJid);
Log.d(Config.LOGTAG, "setting common name for " + jid + " to " + cn);
account.getRoster().getContact(jid).setCommonName(cn);
} catch (final IllegalArgumentException ignored) {
//ignored
}
}
}
this.put(axolotlAddress, new XmppAxolotlSession(account, store, axolotlAddress, identityKey));
}
}
private void fillMap(SQLiteAxolotlStore store) {
List<Integer> deviceIds = store.getSubDeviceSessions(account.getJid().asBareJid().toString());
putDevicesForJid(account.getJid().asBareJid().toString(), deviceIds, store);
for (String address : store.getKnownAddresses()) {
deviceIds = store.getSubDeviceSessions(address);
putDevicesForJid(address, deviceIds, store);
}
}
@Override
public void put(SignalProtocolAddress address, XmppAxolotlSession value) {
super.put(address, value);
value.setNotFresh();
}
public void put(XmppAxolotlSession session) {
this.put(session.getRemoteAddress(), session);
}
}
public enum FetchStatus {
PENDING,
SUCCESS,
SUCCESS_VERIFIED,
TIMEOUT,
SUCCESS_TRUSTED,
ERROR
}
private static class FetchStatusMap extends AxolotlAddressMap<FetchStatus> {
public void clearErrorFor(Jid jid) {
synchronized (MAP_LOCK) {
Map<Integer, FetchStatus> devices = this.map.get(jid.asBareJid().toString());
if (devices == null) {
return;
}
for (Map.Entry<Integer, FetchStatus> entry : devices.entrySet()) {
if (entry.getValue() == FetchStatus.ERROR) {
Log.d(Config.LOGTAG, "resetting error for " + jid.asBareJid() + "(" + entry.getKey() + ")");
entry.setValue(FetchStatus.TIMEOUT);
}
}
}
}
}
public static String getLogprefix(Account account) {
return LOGPREFIX + " (" + account.getJid().asBareJid().toString() + "): ";
}
public AxolotlService(Account account, XmppConnectionService connectionService) {
if (account == null || connectionService == null) {
throw new IllegalArgumentException("account and service cannot be null");
}
if (Security.getProvider("BC") == null) {
Security.addProvider(new BouncyCastleProvider());
}
this.mXmppConnectionService = connectionService;
this.account = account;
this.axolotlStore = new SQLiteAxolotlStore(this.account, this.mXmppConnectionService);
this.deviceIds = new HashMap<>();
this.messageCache = new HashMap<>();
this.sessions = new SessionMap(mXmppConnectionService, axolotlStore, account);
this.fetchStatusMap = new FetchStatusMap();
this.executor = new SerialSingleThreadExecutor("Axolotl");
}
public String getOwnFingerprint() {
return CryptoHelper.bytesToHex(axolotlStore.getIdentityKeyPair().getPublicKey().serialize());
}
@ -359,7 +215,6 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
return s;
}
public Collection<XmppAxolotlSession> findSessionsForContact(Contact contact) {
SignalProtocolAddress contactAddress = getAddressForJid(contact.getJid());
ArrayList<XmppAxolotlSession> s = new ArrayList<>(this.sessions.getAll(contactAddress.getName()).values());
@ -797,7 +652,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
final boolean wipe,
final boolean firstAttempt) {
final Bundle publishOptions = account.getXmppConnection().getFeatures().pepPublishOptions() ? PublishOptions.openAccess() : null;
IqPacket publish = mXmppConnectionService.getIqGenerator().publishBundles(
final IqPacket publish = mXmppConnectionService.getIqGenerator().publishBundles(
signedPreKeyRecord, axolotlStore.getIdentityKeyPair().getPublicKey(),
preKeyRecords, getOwnDeviceId(), publishOptions);
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + ": Bundle " + getOwnDeviceId() + " in PEP not current. Publishing...");
@ -839,6 +694,14 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
});
}
public void deleteOmemoIdentity() {
final String node = AxolotlService.PEP_BUNDLES + ":" + getOwnDeviceId();
final IqPacket deleteBundleNode = mXmppConnectionService.getIqGenerator().deleteNode(node);
mXmppConnectionService.sendIqPacket(account, deleteBundleNode, null);
final Set<Integer> ownDeviceIds = getOwnDeviceIds();
publishDeviceIdsAndRefineAccessModel(ownDeviceIds == null ? Collections.emptySet() : ownDeviceIds);
}
public List<Jid> getCryptoTargets(Conversation conversation) {
final List<Jid> jids;
if (conversation.getMode() == Conversation.MODE_SINGLE) {
@ -916,8 +779,6 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
}
}
private final Set<Integer> PREVIOUSLY_REMOVED_FROM_ANNOUNCEMENT = new HashSet<>();
private void finishBuildingSessionsFromPEP(final SignalProtocolAddress address) {
SignalProtocolAddress ownAddress = new SignalProtocolAddress(account.getJid().asBareJid().toString(), 0);
Map<Integer, FetchStatus> own = fetchStatusMap.getAll(ownAddress.getName());
@ -955,14 +816,6 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
return !hasAny(jid) && (!deviceIds.containsKey(jid) || deviceIds.get(jid).isEmpty());
}
public interface OnDeviceIdsFetched {
void fetched(Jid jid, Set<Integer> deviceIds);
}
public interface OnMultipleDeviceIdFetched {
void fetched();
}
public void fetchDeviceIds(final Jid jid) {
fetchDeviceIds(jid, null);
}
@ -1039,12 +892,6 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
}
}
interface OnSessionBuildFromPep {
void onSessionBuildSuccessful();
void onSessionBuildFailed();
}
private void buildSessionFromPEP(final SignalProtocolAddress address) {
buildSessionFromPEP(address, null);
}
@ -1392,7 +1239,6 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
private XmppAxolotlSession getReceivingSession(SignalProtocolAddress senderAddress) {
XmppAxolotlSession session = sessions.get(senderAddress);
if (session == null) {
//Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Account: " + account.getJid() + " No axolotl session found while parsing received message " + message);
session = recreateUncachedSession(senderAddress);
if (session == null) {
session = new XmppAxolotlSession(account, axolotlStore, senderAddress);
@ -1401,7 +1247,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
return session;
}
public XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceivingPayloadMessage(XmppAxolotlMessage message, boolean postponePreKeyMessageHandling) throws NotEncryptedForThisDeviceException, BrokenSessionException {
public XmppAxolotlMessage.XmppAxolotlPlaintextMessage processReceivingPayloadMessage(XmppAxolotlMessage message, boolean postponePreKeyMessageHandling) throws NotEncryptedForThisDeviceException, BrokenSessionException, OutdatedSenderException {
XmppAxolotlMessage.XmppAxolotlPlaintextMessage plaintextMessage = null;
XmppAxolotlSession session = getReceivingSession(message);
@ -1420,6 +1266,9 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
}
} catch (final BrokenSessionException e) {
throw e;
} catch (final OutdatedSenderException e) {
Log.e(Config.LOGTAG, account.getJid().asBareJid() + ": " + e.getMessage());
throw e;
} catch (CryptoFailedException e) {
Log.w(Config.LOGTAG, getLogprefix(account) + "Failed to decrypt message from " + message.getFrom(), e);
}
@ -1469,7 +1318,9 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
} else {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": nothing to flush. Not republishing key");
}
completeSession(session);
if (trustedOrPreviouslyResponded(session)) {
completeSession(session);
}
}
}
@ -1479,23 +1330,43 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
publishBundlesIfNeeded(false, false);
}
}
Iterator<XmppAxolotlSession> iterator = postponedSessions.iterator();
final Iterator<XmppAxolotlSession> iterator = postponedSessions.iterator();
while (iterator.hasNext()) {
completeSession(iterator.next());
final XmppAxolotlSession session = iterator.next();
if (trustedOrPreviouslyResponded(session)) {
completeSession(session);
}
iterator.remove();
}
Iterator<SignalProtocolAddress> postponedHealingAttemptsIterator = postponedHealing.iterator();
final Iterator<SignalProtocolAddress> postponedHealingAttemptsIterator = postponedHealing.iterator();
while (postponedHealingAttemptsIterator.hasNext()) {
notifyRequiresHealing(postponedHealingAttemptsIterator.next());
postponedHealingAttemptsIterator.remove();
}
}
private boolean trustedOrPreviouslyResponded(XmppAxolotlSession session) {
try {
return trustedOrPreviouslyResponded(Jid.of(session.getRemoteAddress().getName()));
} catch (IllegalArgumentException e) {
return false;
}
}
public boolean trustedOrPreviouslyResponded(Jid jid) {
final Contact contact = account.getRoster().getContact(jid);
if (contact.showInRoster() || contact.isSelf()) {
return true;
}
final Conversation conversation = mXmppConnectionService.find(account, jid);
return conversation != null && conversation.sentMessagesCount() > 0;
}
private void completeSession(XmppAxolotlSession session) {
final XmppAxolotlMessage axolotlMessage = new XmppAxolotlMessage(account.getJid().asBareJid(), getOwnDeviceId());
axolotlMessage.addDevice(session, true);
try {
Jid jid = Jid.of(session.getRemoteAddress().getName());
final Jid jid = Jid.of(session.getRemoteAddress().getName());
MessagePacket packet = mXmppConnectionService.getMessageGenerator().generateKeyTransportMessage(jid, axolotlMessage);
mXmppConnectionService.sendMessagePacket(account, packet);
} catch (IllegalArgumentException e) {
@ -1503,11 +1374,9 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
}
}
public XmppAxolotlMessage.XmppAxolotlKeyTransportMessage processReceivingKeyTransportMessage(XmppAxolotlMessage message, final boolean postponePreKeyMessageHandling) {
XmppAxolotlMessage.XmppAxolotlKeyTransportMessage keyTransportMessage;
XmppAxolotlSession session = getReceivingSession(message);
final XmppAxolotlMessage.XmppAxolotlKeyTransportMessage keyTransportMessage;
final XmppAxolotlSession session = getReceivingSession(message);
try {
keyTransportMessage = message.getParameters(session, getOwnDeviceId());
Integer preKeyId = session.getPreKeyIdAndReset();
@ -1516,7 +1385,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
}
} catch (CryptoFailedException e) {
Log.d(Config.LOGTAG, "could not decrypt keyTransport message " + e.getMessage());
keyTransportMessage = null;
return null;
}
if (session.isFresh() && keyTransportMessage != null) {
@ -1527,7 +1396,6 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
}
private void putFreshSession(XmppAxolotlSession session) {
Log.d(Config.LOGTAG, "put fresh session");
sessions.put(session);
if (Config.X509_VERIFICATION) {
if (session.getIdentityKey() != null) {
@ -1537,4 +1405,164 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
}
}
}
public enum FetchStatus {
PENDING,
SUCCESS,
SUCCESS_VERIFIED,
TIMEOUT,
SUCCESS_TRUSTED,
ERROR
}
public interface OnDeviceIdsFetched {
void fetched(Jid jid, Set<Integer> deviceIds);
}
public interface OnMultipleDeviceIdFetched {
void fetched();
}
interface OnSessionBuildFromPep {
void onSessionBuildSuccessful();
void onSessionBuildFailed();
}
private static class AxolotlAddressMap<T> {
protected final Object MAP_LOCK = new Object();
protected Map<String, Map<Integer, T>> map;
public AxolotlAddressMap() {
this.map = new HashMap<>();
}
public void put(SignalProtocolAddress address, T value) {
synchronized (MAP_LOCK) {
Map<Integer, T> devices = map.get(address.getName());
if (devices == null) {
devices = new HashMap<>();
map.put(address.getName(), devices);
}
devices.put(address.getDeviceId(), value);
}
}
public T get(SignalProtocolAddress address) {
synchronized (MAP_LOCK) {
Map<Integer, T> devices = map.get(address.getName());
if (devices == null) {
return null;
}
return devices.get(address.getDeviceId());
}
}
public Map<Integer, T> getAll(String name) {
synchronized (MAP_LOCK) {
Map<Integer, T> devices = map.get(name);
if (devices == null) {
return new HashMap<>();
}
return devices;
}
}
public boolean hasAny(SignalProtocolAddress address) {
synchronized (MAP_LOCK) {
Map<Integer, T> devices = map.get(address.getName());
return devices != null && !devices.isEmpty();
}
}
public void clear() {
map.clear();
}
}
private static class SessionMap extends AxolotlAddressMap<XmppAxolotlSession> {
private final XmppConnectionService xmppConnectionService;
private final Account account;
public SessionMap(XmppConnectionService service, SQLiteAxolotlStore store, Account account) {
super();
this.xmppConnectionService = service;
this.account = account;
this.fillMap(store);
}
public Set<Jid> findCounterpartsForSourceId(Integer sid) {
Set<Jid> candidates = new HashSet<>();
synchronized (MAP_LOCK) {
for (Map.Entry<String, Map<Integer, XmppAxolotlSession>> entry : map.entrySet()) {
String key = entry.getKey();
if (entry.getValue().containsKey(sid)) {
candidates.add(Jid.of(key));
}
}
}
return candidates;
}
private void putDevicesForJid(String bareJid, List<Integer> deviceIds, SQLiteAxolotlStore store) {
for (Integer deviceId : deviceIds) {
SignalProtocolAddress axolotlAddress = new SignalProtocolAddress(bareJid, deviceId);
IdentityKey identityKey = store.loadSession(axolotlAddress).getSessionState().getRemoteIdentityKey();
if (Config.X509_VERIFICATION) {
X509Certificate certificate = store.getFingerprintCertificate(CryptoHelper.bytesToHex(identityKey.getPublicKey().serialize()));
if (certificate != null) {
Bundle information = CryptoHelper.extractCertificateInformation(certificate);
try {
final String cn = information.getString("subject_cn");
final Jid jid = Jid.of(bareJid);
Log.d(Config.LOGTAG, "setting common name for " + jid + " to " + cn);
account.getRoster().getContact(jid).setCommonName(cn);
} catch (final IllegalArgumentException ignored) {
//ignored
}
}
}
this.put(axolotlAddress, new XmppAxolotlSession(account, store, axolotlAddress, identityKey));
}
}
private void fillMap(SQLiteAxolotlStore store) {
List<Integer> deviceIds = store.getSubDeviceSessions(account.getJid().asBareJid().toString());
putDevicesForJid(account.getJid().asBareJid().toString(), deviceIds, store);
for (String address : store.getKnownAddresses()) {
deviceIds = store.getSubDeviceSessions(address);
putDevicesForJid(address, deviceIds, store);
}
}
@Override
public void put(SignalProtocolAddress address, XmppAxolotlSession value) {
super.put(address, value);
value.setNotFresh();
}
public void put(XmppAxolotlSession session) {
this.put(session.getRemoteAddress(), session);
}
}
private static class FetchStatusMap extends AxolotlAddressMap<FetchStatus> {
public void clearErrorFor(Jid jid) {
synchronized (MAP_LOCK) {
Map<Integer, FetchStatus> devices = this.map.get(jid.asBareJid().toString());
if (devices == null) {
return;
}
for (Map.Entry<Integer, FetchStatus> entry : devices.entrySet()) {
if (entry.getValue() == FetchStatus.ERROR) {
Log.d(Config.LOGTAG, "resetting error for " + jid.asBareJid() + "(" + entry.getKey() + ")");
entry.setValue(FetchStatus.TIMEOUT);
}
}
}
}
}
}

View file

@ -0,0 +1,8 @@
package de.pixart.messenger.crypto.axolotl;
public class OutdatedSenderException extends CryptoFailedException {
public OutdatedSenderException(final String msg) {
super(msg);
}
}

View file

@ -37,69 +37,13 @@ public class XmppAxolotlMessage {
private static final String KEYTYPE = "AES";
private static final String CIPHERMODE = "AES/GCM/NoPadding";
private static final String PROVIDER = "BC";
private final List<XmppAxolotlSession.AxolotlKey> keys;
private final Jid from;
private final int sourceDeviceId;
private byte[] innerKey;
private byte[] ciphertext = null;
private byte[] authtagPlusInnerKey = null;
private byte[] iv = null;
private final List<XmppAxolotlSession.AxolotlKey> keys;
private final Jid from;
private final int sourceDeviceId;
public static class XmppAxolotlPlaintextMessage {
private final String plaintext;
private final String fingerprint;
XmppAxolotlPlaintextMessage(String plaintext, String fingerprint) {
this.plaintext = plaintext;
this.fingerprint = fingerprint;
}
public String getPlaintext() {
return plaintext;
}
public String getFingerprint() {
return fingerprint;
}
}
public static class XmppAxolotlKeyTransportMessage {
private final String fingerprint;
private final byte[] key;
private final byte[] iv;
XmppAxolotlKeyTransportMessage(String fingerprint, byte[] key, byte[] iv) {
this.fingerprint = fingerprint;
this.key = key;
this.iv = iv;
}
public String getFingerprint() {
return fingerprint;
}
public byte[] getKey() {
return key;
}
public byte[] getIv() {
return iv;
}
}
public static int parseSourceId(final Element axolotlMessage) throws IllegalArgumentException {
final Element header = axolotlMessage.findChild(HEADER);
if (header == null) {
throw new IllegalArgumentException("No header found");
}
try {
return Integer.parseInt(header.getAttribute(SOURCEID));
} catch (NumberFormatException e) {
throw new IllegalArgumentException("invalid source id");
}
}
private XmppAxolotlMessage(final Element axolotlMessage, final Jid from) throws IllegalArgumentException {
this.from = from;
@ -134,7 +78,7 @@ public class XmppAxolotlMessage {
break;
}
}
Element payloadElement = axolotlMessage.findChild(PAYLOAD);
final Element payloadElement = axolotlMessage.findChildEnsureSingle(PAYLOAD, AxolotlService.PEP_PREFIX);
if (payloadElement != null) {
ciphertext = Base64.decode(payloadElement.getContent().trim(), Base64.DEFAULT);
}
@ -148,6 +92,18 @@ public class XmppAxolotlMessage {
this.innerKey = generateKey();
}
public static int parseSourceId(final Element axolotlMessage) throws IllegalArgumentException {
final Element header = axolotlMessage.findChild(HEADER);
if (header == null) {
throw new IllegalArgumentException("No header found");
}
try {
return Integer.parseInt(header.getAttribute(SOURCEID));
} catch (NumberFormatException e) {
throw new IllegalArgumentException("invalid source id");
}
}
public static XmppAxolotlMessage fromElement(Element element, Jid from) {
return new XmppAxolotlMessage(element, from);
}
@ -164,12 +120,28 @@ public class XmppAxolotlMessage {
}
private static byte[] generateIv() {
SecureRandom random = new SecureRandom();
byte[] iv = new byte[16];
final SecureRandom random = new SecureRandom();
final byte[] iv = new byte[12];
random.nextBytes(iv);
return iv;
}
private static byte[] getPaddedBytes(String plaintext) {
int plainLength = plaintext.getBytes().length;
int pad = Math.max(64, (plainLength / 32 + 1) * 32) - plainLength;
SecureRandom random = new SecureRandom();
int left = random.nextInt(pad);
int right = pad - left;
StringBuilder builder = new StringBuilder(plaintext);
for (int i = 0; i < left; ++i) {
builder.insert(0, random.nextBoolean() ? "\t" : " ");
}
for (int i = 0; i < right; ++i) {
builder.append(random.nextBoolean() ? "\t" : " ");
}
return builder.toString().getBytes();
}
public boolean hasPayload() {
return ciphertext != null;
}
@ -196,22 +168,6 @@ public class XmppAxolotlMessage {
}
}
private static byte[] getPaddedBytes(String plaintext) {
int plainLength = plaintext.getBytes().length;
int pad = Math.max(64, (plainLength / 32 + 1) * 32) - plainLength;
SecureRandom random = new SecureRandom();
int left = random.nextInt(pad);
int right = pad - left;
StringBuilder builder = new StringBuilder(plaintext);
for (int i = 0; i < left; ++i) {
builder.insert(0, random.nextBoolean() ? "\t" : " ");
}
for (int i = 0; i < right; ++i) {
builder.append(random.nextBoolean() ? "\t" : " ");
}
return builder.toString().getBytes();
}
public Jid getFrom() {
return this.from;
}
@ -287,19 +243,19 @@ public class XmppAxolotlMessage {
byte[] key = unpackKey(session, sourceDeviceId);
if (key != null) {
try {
if (key.length >= 32) {
int authtaglength = key.length - 16;
Log.d(Config.LOGTAG, "found auth tag as part of omemo key");
byte[] newCipherText = new byte[key.length - 16 + ciphertext.length];
byte[] newKey = new byte[16];
System.arraycopy(ciphertext, 0, newCipherText, 0, ciphertext.length);
System.arraycopy(key, 16, newCipherText, ciphertext.length, authtaglength);
System.arraycopy(key, 0, newKey, 0, newKey.length);
ciphertext = newCipherText;
key = newKey;
if (key.length < 32) {
throw new OutdatedSenderException("Key did not contain auth tag. Sender needs to update their OMEMO client");
}
final int authTagLength = key.length - 16;
byte[] newCipherText = new byte[key.length - 16 + ciphertext.length];
byte[] newKey = new byte[16];
System.arraycopy(ciphertext, 0, newCipherText, 0, ciphertext.length);
System.arraycopy(key, 16, newCipherText, ciphertext.length, authTagLength);
System.arraycopy(key, 0, newKey, 0, newKey.length);
ciphertext = newCipherText;
key = newKey;
Cipher cipher = Compatibility.twentyEight() ? Cipher.getInstance(CIPHERMODE) : Cipher.getInstance(CIPHERMODE, PROVIDER);
final Cipher cipher = Compatibility.twentyEight() ? Cipher.getInstance(CIPHERMODE) : Cipher.getInstance(CIPHERMODE, PROVIDER);
SecretKeySpec keySpec = new SecretKeySpec(key, KEYTYPE);
IvParameterSpec ivSpec = new IvParameterSpec(iv);
@ -316,4 +272,47 @@ public class XmppAxolotlMessage {
}
return plaintextMessage;
}
public static class XmppAxolotlPlaintextMessage {
private final String plaintext;
private final String fingerprint;
XmppAxolotlPlaintextMessage(String plaintext, String fingerprint) {
this.plaintext = plaintext;
this.fingerprint = fingerprint;
}
public String getPlaintext() {
return plaintext;
}
public String getFingerprint() {
return fingerprint;
}
}
public static class XmppAxolotlKeyTransportMessage {
private final String fingerprint;
private final byte[] key;
private final byte[] iv;
XmppAxolotlKeyTransportMessage(String fingerprint, byte[] key, byte[] iv) {
this.fingerprint = fingerprint;
this.key = key;
this.iv = iv;
}
public String getFingerprint() {
return fingerprint;
}
public byte[] getKey() {
return key;
}
public byte[] getIv() {
return iv;
}
}
}

View file

@ -1,9 +1,10 @@
package de.pixart.messenger.crypto.axolotl;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import org.whispersystems.libsignal.DuplicateMessageException;
import org.whispersystems.libsignal.IdentityKey;
import org.whispersystems.libsignal.InvalidKeyException;
@ -115,7 +116,13 @@ public class XmppAxolotlSession implements Comparable<XmppAxolotlSession> {
SignalMessage signalMessage = new SignalMessage(encryptedKey.key);
try {
plaintext = cipher.decrypt(signalMessage);
} catch (InvalidMessageException | NoSessionException e) {
} catch (InvalidMessageException e) {
if (iterator.hasNext()) {
Log.w(Config.LOGTAG, account.getJid().asBareJid() + ": ignoring crypto exception because possible keys left to try", e);
continue;
}
throw new BrokenSessionException(this.remoteAddress, e);
} catch (NoSessionException e) {
if (iterator.hasNext()) {
Log.w(Config.LOGTAG, account.getJid().asBareJid() + ": ignoring crypto exception because possible keys left to try", e);
continue;

View file

@ -43,7 +43,9 @@ abstract class ScramMechanism extends SaslMechanism {
clientKey = hmac(saltedPassword, CLIENT_KEY_BYTES);
return new KeyPair(clientKey, serverKey);
} catch (final InvalidKeyException | NumberFormatException e) {
} catch (final InvalidKeyException e) {
return null;
} catch (final NumberFormatException e) {
return null;
}
}

View file

@ -16,11 +16,12 @@ import java.security.PublicKey;
import java.security.interfaces.DSAPublicKey;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
import de.pixart.messenger.Config;
@ -55,6 +56,7 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable
public static final String RESOURCE = "resource";
public static final String PINNED_MECHANISM_KEY = "pinned_mechanism";
public static final String PRE_AUTH_REGISTRATION_TOKEN = "pre_auth_registration";
public static final int OPTION_USETLS = 0;
public static final int OPTION_DISABLED = 1;
@ -64,15 +66,18 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable
public static final int OPTION_REQUIRES_ACCESS_MODE_CHANGE = 5;
public static final int OPTION_LOGGED_IN_SUCCESSFULLY = 6;
public static final int OPTION_HTTP_UPLOAD_AVAILABLE = 7;
public static final int OPTION_UNVERIFIED = 8;
public static final int OPTION_FIXED_USERNAME = 9;
private static final String KEY_PGP_SIGNATURE = "pgp_signature";
private static final String KEY_PGP_ID = "pgp_id";
public final HashSet<Pair<String, String>> inProgressDiscoFetches = new HashSet<>();
protected final JSONObject keys;
private final Roster roster = new Roster(this);
private final Collection<Jid> blocklist = new CopyOnWriteArraySet<>();
public List<Conversation> pendingConferenceJoins = new CopyOnWriteArrayList<>();
public List<Conversation> pendingConferenceLeaves = new CopyOnWriteArrayList<>();
public final Set<Conversation> pendingConferenceJoins = new HashSet<>();
public final Set<Conversation> pendingConferenceLeaves = new HashSet<>();
public final Set<Conversation> inProgressConferenceJoins = new HashSet<>();
public final Set<Conversation> inProgressConferencePings = new HashSet<>();
protected Jid jid;
protected String password;
protected int options = 0;
@ -91,7 +96,7 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable
private XmppConnection xmppConnection = null;
private long mEndGracePeriod = 0L;
private String otrFingerprint;
private List<Bookmark> bookmarks = new CopyOnWriteArrayList<>();
private final Map<Jid, Bookmark> bookmarks = new HashMap<>();
private Presence.Status presenceStatus = Presence.Status.ONLINE;
private String presenceStatusMessage = null;
@ -501,36 +506,45 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable
return this.roster;
}
public List<Bookmark> getBookmarks() {
return this.bookmarks;
public Collection<Bookmark> getBookmarks() {
return this.bookmarks.values();
}
public void setBookmarks(final CopyOnWriteArrayList<Bookmark> bookmarks) {
this.bookmarks = bookmarks;
public void setBookmarks(Map<Jid, Bookmark> bookmarks) {
synchronized (this.bookmarks) {
this.bookmarks.clear();
this.bookmarks.putAll(bookmarks);
}
}
public void putBookmark(Bookmark bookmark) {
synchronized (this.bookmarks) {
this.bookmarks.put(bookmark.getJid(), bookmark);
}
}
public void removeBookmark(Bookmark bookmark) {
synchronized (this.bookmarks) {
this.bookmarks.remove(bookmark.getJid());
}
}
public void removeBookmark(Jid jid) {
synchronized (this.bookmarks) {
this.bookmarks.remove(jid);
}
}
public Set<Jid> getBookmarkedJids() {
final Set<Jid> jids = new HashSet<>();
for (final Bookmark bookmark : this.bookmarks) {
final Jid jid = bookmark.getJid();
if (jid != null) {
jids.add(jid.asBareJid());
}
synchronized (this.bookmarks) {
return new HashSet<>(this.bookmarks.keySet());
}
return jids;
}
public boolean hasBookmarkFor(final Jid conferenceJid) {
return getBookmark(conferenceJid) != null;
}
Bookmark getBookmark(final Jid jid) {
for (final Bookmark bookmark : this.bookmarks) {
if (bookmark.getJid() != null && jid.asBareJid().equals(bookmark.getJid().asBareJid())) {
return bookmark;
}
public Bookmark getBookmark(final Jid jid) {
synchronized (this.bookmarks) {
return this.bookmarks.get(jid.asBareJid());
}
return null;
}
public boolean setAvatar(final String filename) {
@ -638,6 +652,7 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable
REGISTRATION_CONFLICT(true, false),
REGISTRATION_NOT_SUPPORTED(true, false),
REGISTRATION_PLEASE_WAIT(true, false),
REGISTRATION_INVALID_TOKEN(true, false),
REGISTRATION_PASSWORD_TOO_WEAK(true, false),
TLS_ERROR,
INCOMPATIBLE_SERVER,
@ -702,6 +717,8 @@ public class Account extends AbstractEntity implements AvatarService.Avatarable
return R.string.account_status_regis_success;
case REGISTRATION_NOT_SUPPORTED:
return R.string.account_status_regis_not_sup;
case REGISTRATION_INVALID_TOKEN:
return R.string.account_status_regis_invalid_token;
case TLS_ERROR:
return R.string.account_status_tls_error;
case INCOMPATIBLE_SERVER:

View file

@ -1,14 +1,19 @@
package de.pixart.messenger.entities;
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.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import de.pixart.messenger.utils.Namespace;
import de.pixart.messenger.utils.StringUtils;
import de.pixart.messenger.utils.UIHelper;
import de.pixart.messenger.xml.Element;
@ -33,20 +38,70 @@ public class Bookmark extends Element implements ListItem {
this.account = account;
}
public static Map<Jid, Bookmark> parseFromStorage(Element storage, Account account) {
if (storage == null) {
return Collections.emptyMap();
}
final HashMap<Jid, Bookmark> bookmarks = new HashMap<>();
for (final Element item : storage.getChildren()) {
if (item.getName().equals("conference")) {
final Bookmark bookmark = Bookmark.parse(item, account);
if (bookmark != null) {
final Bookmark old = bookmarks.put(bookmark.jid, bookmark);
if (old != null && old.getBookmarkName() != null && bookmark.getBookmarkName() == null) {
bookmark.setBookmarkName(old.getBookmarkName());
}
}
}
}
return bookmarks;
}
public static Map<Jid, Bookmark> parseFromPubsub(Element pubsub, Account account) {
if (pubsub == null) {
return Collections.emptyMap();
}
final Element items = pubsub.findChild("items");
if (items != null && Namespace.BOOKMARKS2.equals(items.getAttribute("node"))) {
final Map<Jid, Bookmark> bookmarks = new HashMap<>();
for (Element item : items.getChildren()) {
if (item.getName().equals("item")) {
final Bookmark bookmark = Bookmark.parseFromItem(item, account);
if (bookmark != null) {
bookmarks.put(bookmark.jid, bookmark);
}
}
}
return bookmarks;
}
return Collections.emptyMap();
}
public static Bookmark parse(Element element, Account account) {
Bookmark bookmark = new Bookmark(account);
bookmark.setAttributes(element.getAttributes());
bookmark.setChildren(element.getChildren());
bookmark.jid = InvalidJid.getNullForInvalid(bookmark.getAttributeAsJid("jid"));
if (bookmark.jid == null) {
return null;
}
return bookmark;
}
public static boolean printableValue(@Nullable String value, boolean permitNone) {
return value != null && !value.trim().isEmpty() && (permitNone || !"None".equals(value));
}
public static boolean printableValue(@Nullable String value) {
return printableValue(value, true);
public static Bookmark parseFromItem(Element item, Account account) {
final Element conference = item.findChild("conference", Namespace.BOOKMARKS2);
if (conference == null) {
return null;
}
final Bookmark bookmark = new Bookmark(account);
bookmark.jid = InvalidJid.getNullForInvalid(item.getAttributeAsJid("id"));
if (bookmark.jid == null) {
return null;
}
bookmark.setBookmarkName(conference.getAttribute("name"));
bookmark.setAutojoin(conference.getAttributeAsBoolean("autojoin"));
bookmark.setNick(conference.findChildContent("nick"));
return bookmark;
}
public void setAutojoin(boolean autojoin) {
@ -82,6 +137,14 @@ public class Bookmark extends Element implements ListItem {
return 0;
}
public static boolean printableValue(@Nullable String value, boolean permitNone) {
return value != null && !value.trim().isEmpty() && (permitNone || !"None".equals(value));
}
public static boolean printableValue(@Nullable String value) {
return printableValue(value, true);
}
@Override
public Jid getJid() {
return this.jid;
@ -191,4 +254,4 @@ public class Bookmark extends Element implements ListItem {
public int getAvatarBackgroundColor() {
return UIHelper.getColorForName(jid != null ? jid.asBareJid().toString() : getDisplayName());
}
}
}

View file

@ -4,9 +4,10 @@ import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@ -20,6 +21,7 @@ import java.util.Locale;
import de.pixart.messenger.Config;
import de.pixart.messenger.R;
import de.pixart.messenger.android.AbstractPhoneContact;
import de.pixart.messenger.services.QuickConversationsService;
import de.pixart.messenger.utils.JidHelper;
import de.pixart.messenger.utils.UIHelper;
import de.pixart.messenger.xml.Element;
@ -101,7 +103,7 @@ public class Contact implements ListItem, Blockable {
final Jid jid;
try {
jid = Jid.of(cursor.getString(cursor.getColumnIndex(JID)));
} catch (final IllegalArgumentException e) {
} catch (final IllegalArgumentException e) {
// TODO: Borked DB... handle this somehow?
return null;
}
@ -384,7 +386,9 @@ public class Contact implements ListItem, Blockable {
}
public boolean showInContactList() {
return showInRoster() || getOption(Options.SYNCED_VIA_OTHER);
return showInRoster()
|| getOption(Options.SYNCED_VIA_OTHER)
|| (QuickConversationsService.isQuicksy() && systemAccount != null);
}
public void parseSubscriptionFromElement(Element item) {
@ -587,7 +591,8 @@ public class Contact implements ListItem, Blockable {
changed |= setPhotoUri(phoneContact.getPhotoUri());
return changed;
}
public synchronized boolean unsetPhoneContact(Class<?extends AbstractPhoneContact> clazz) {
public synchronized boolean unsetPhoneContact(Class<? extends AbstractPhoneContact> clazz) {
resetOption(getOption(clazz));
boolean changed = false;
if (!getOption(Options.SYNCED_VIA_ADDRESSBOOK) && !getOption(Options.SYNCED_VIA_OTHER)) {
@ -597,6 +602,7 @@ public class Contact implements ListItem, Blockable {
}
return changed;
}
public static int getOption(Class<? extends AbstractPhoneContact> clazz) {
return Options.SYNCED_VIA_OTHER;
}

View file

@ -2,10 +2,11 @@ package de.pixart.messenger.entities;
import android.content.ContentValues;
import android.database.Cursor;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import net.java.otr4j.OtrException;
import net.java.otr4j.crypto.OtrCryptoException;
import net.java.otr4j.session.SessionID;
@ -57,6 +58,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
public static final String ATTRIBUTE_MUTED_TILL = "muted_till";
public static final String ATTRIBUTE_ALWAYS_NOTIFY = "always_notify";
public static final String ATTRIBUTE_PUSH_NODE = "push_node";
public static final String ATTRIBUTE_LAST_CLEAR_HISTORY = "last_clear_history";
static final String ATTRIBUTE_MUC_PASSWORD = "muc_password";
private static final String ATTRIBUTE_NEXT_MESSAGE = "next_message";
@ -87,8 +89,8 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
private transient MucOptions mucOptions = null;
private byte[] symmetricKey;
private boolean messagesLeftOnServer = true;
private ChatState mOutgoingChatState = Config.DEFAULT_CHATSTATE;
private ChatState mIncomingChatState = Config.DEFAULT_CHATSTATE;
private ChatState mOutgoingChatState = Config.DEFAULT_CHAT_STATE;
private ChatState mIncomingChatState = Config.DEFAULT_CHAT_STATE;
private String mLastReceivedOtrMessageId = null;
private String mFirstMamReference = null;
@ -139,7 +141,9 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
}
public void deleteMessage(Message message) {
this.messages.remove(message);
synchronized (this.messages) {
this.messages.remove(message);
}
}
public Message getFirstUnreadMessage() {
@ -338,7 +342,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
final ArrayList<Message> results = new ArrayList<>();
synchronized (this.messages) {
for (Message message : this.messages) {
if (message.getType() != Message.TYPE_IMAGE && message.getStatus() == Message.STATUS_UNSEND) {
if ((message.getType() == Message.TYPE_TEXT || message.hasFileOnRemoteHost()) && message.getStatus() == Message.STATUS_UNSEND) {
results.add(message);
}
}
@ -364,11 +368,18 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
public Message findMessageWithRemoteIdAndCounterpart(String id, Jid counterpart, boolean received, boolean carbon) {
synchronized (this.messages) {
for (int i = this.messages.size() - 1; i >= 0; --i) {
Message message = messages.get(i);
if (counterpart.equals(message.getCounterpart())
&& ((message.getStatus() == Message.STATUS_RECEIVED) == received)
final Message message = messages.get(i);
final Jid mcp = message.getCounterpart();
if (mcp == null) {
continue;
}
final boolean counterpartMatch = mode == MODE_SINGLE ?
counterpart.asBareJid().equals(mcp.asBareJid()) :
counterpart.equals(mcp);
if (counterpartMatch && ((message.getStatus() == Message.STATUS_RECEIVED) == received)
&& (carbon == message.isCarbon() || received)) {
if (id.equals(message.getRemoteMsgId()) && !message.isFileOrImage() && !message.treatAsDownloadable()) {
final boolean idMatch = id.equals(message.getRemoteMsgId()) || message.remoteMsgIdMatchInEdit(id);
if (idMatch && !message.isFileOrImage() && !message.treatAsDownloadable()) {
return message;
} else {
return null;
@ -736,7 +747,9 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
}
DSAPublicKey remotePubKey = (DSAPublicKey) getOtrSession().getRemotePublicKey();
this.otrFingerprint = getAccount().getOtrService().getFingerprint(remotePubKey).toLowerCase(Locale.US);
} catch (final OtrCryptoException | UnsupportedOperationException ignored) {
} catch (final OtrCryptoException ignored) {
return null;
} catch (final UnsupportedOperationException ignored) {
return null;
}
}
@ -798,6 +811,9 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
if (OmemoSetting.isAlways()) {
return suitableForOmemoByDefault(this) ? Message.ENCRYPTION_AXOLOTL : Message.ENCRYPTION_NONE;
}
if (OmemoSetting.isNever()) {
return Message.ENCRYPTION_NONE;
}
final int defaultEncryption;
if (suitableForOmemoByDefault(this)) {
defaultEncryption = OmemoSetting.getEncryption();

View file

@ -1,7 +1,10 @@
package de.pixart.messenger.entities;
import android.util.Log;
import java.io.File;
import de.pixart.messenger.Config;
import de.pixart.messenger.utils.MimeUtils;
public class DownloadableFile extends File {
@ -67,6 +70,7 @@ public class DownloadableFile extends File {
this.iv = new byte[]{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0xf};
System.arraycopy(keyIvCombo, 0, aeskey, 0, 32);
}
Log.d(Config.LOGTAG, "using " + this.iv.length + "-byte IV for file transmission");
}
public void setKey(byte[] key) {

View file

@ -0,0 +1,97 @@
package de.pixart.messenger.entities;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.List;
public class Edit {
private final String editedId;
private final String serverMsgId;
Edit(String editedId, String serverMsgId) {
this.editedId = editedId;
this.serverMsgId = serverMsgId;
}
static String toJson(List<Edit> edits) throws JSONException {
JSONArray jsonArray = new JSONArray();
for (Edit edit : edits) {
jsonArray.put(edit.toJson());
}
return jsonArray.toString();
}
static boolean wasPreviouslyEditedRemoteMsgId(List<Edit> edits, String remoteMsgId) {
for (Edit edit : edits) {
if (edit.editedId != null && edit.editedId.equals(remoteMsgId)) {
return true;
}
}
return false;
}
static boolean wasPreviouslyEditedServerMsgId(List<Edit> edits, String serverMsgId) {
for (Edit edit : edits) {
if (edit.serverMsgId != null && edit.serverMsgId.equals(serverMsgId)) {
return true;
}
}
return false;
}
private static Edit fromJson(JSONObject jsonObject) throws JSONException {
String edited = jsonObject.has("edited_id") ? jsonObject.getString("edited_id") : null;
String serverMsgId = jsonObject.has("server_msg_id") ? jsonObject.getString("server_msg_id") : null;
return new Edit(edited, serverMsgId);
}
static List<Edit> fromJson(String input) {
final ArrayList<Edit> list = new ArrayList<>();
if (input == null) {
return list;
}
try {
final JSONArray jsonArray = new JSONArray(input);
for (int i = 0; i < jsonArray.length(); ++i) {
list.add(fromJson(jsonArray.getJSONObject(i)));
}
return list;
} catch (JSONException e) {
return list;
}
}
private JSONObject toJson() throws JSONException {
JSONObject jsonObject = new JSONObject();
jsonObject.put("edited_id", editedId);
jsonObject.put("server_msg_id", serverMsgId);
return jsonObject;
}
String getEditedId() {
return editedId;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Edit edit = (Edit) o;
if (editedId != null ? !editedId.equals(edit.editedId) : edit.editedId != null)
return false;
return serverMsgId != null ? serverMsgId.equals(edit.serverMsgId) : edit.serverMsgId == null;
}
@Override
public int hashCode() {
int result = editedId != null ? editedId.hashCode() : 0;
result = 31 * result + (serverMsgId != null ? serverMsgId.hashCode() : 0);
return result;
}
}

View file

@ -41,8 +41,8 @@ public class IndividualMessage extends Message {
super(conversation);
}
private IndividualMessage(Conversational conversation, String uuid, String conversationUUid, Jid counterpart, Jid trueCounterpart, String body, long timeSent, int encryption, int status, int type, boolean carbon, String remoteMsgId, String relativeFilePath, String serverMsgId, String fingerprint, boolean read, boolean deleted, String edited, boolean oob, String errorMessage, Set<ReadByMarker> readByMarkers, boolean markable, boolean file_deleted) {
super(conversation, uuid, conversationUUid, counterpart, trueCounterpart, body, timeSent, encryption, status, type, carbon, remoteMsgId, relativeFilePath, serverMsgId, fingerprint, read, deleted, edited, oob, errorMessage, readByMarkers, markable, file_deleted);
private IndividualMessage(Conversational conversation, String uuid, String conversationUUid, Jid counterpart, Jid trueCounterpart, String body, long timeSent, int encryption, int status, int type, boolean carbon, String remoteMsgId, String relativeFilePath, String serverMsgId, String fingerprint, boolean read, boolean deleted, String edited, boolean oob, String errorMessage, Set<ReadByMarker> readByMarkers, boolean markable, boolean file_deleted, String bodyLanguage) {
super(conversation, uuid, conversationUUid, counterpart, trueCounterpart, body, timeSent, encryption, status, type, carbon, remoteMsgId, relativeFilePath, serverMsgId, fingerprint, read, deleted, edited, oob, errorMessage, readByMarkers, markable, file_deleted, bodyLanguage);
}
public static Message createDateSeparator(Message message) {
@ -100,7 +100,9 @@ public class IndividualMessage extends Message {
cursor.getString(cursor.getColumnIndex(ERROR_MESSAGE)),
ReadByMarker.fromJsonString(cursor.getString(cursor.getColumnIndex(READ_BY_MARKERS))),
cursor.getInt(cursor.getColumnIndex(MARKABLE)) > 0,
cursor.getInt(cursor.getColumnIndex(FILE_DELETED)) > 0);
cursor.getInt(cursor.getColumnIndex(FILE_DELETED)) > 0,
cursor.getString(cursor.getColumnIndex(BODY_LANGUAGE))
);
}
@Override

View file

@ -5,22 +5,22 @@ import android.database.Cursor;
import android.graphics.Color;
import android.text.SpannableStringBuilder;
import android.util.Log;
import android.webkit.URLUtil;
import org.json.JSONException;
import java.lang.ref.WeakReference;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import de.pixart.messenger.Config;
import de.pixart.messenger.crypto.axolotl.FingerprintStatus;
import de.pixart.messenger.services.AvatarService;
import de.pixart.messenger.ui.util.MyLinkify;
import de.pixart.messenger.utils.CryptoHelper;
import de.pixart.messenger.utils.Emoticons;
import de.pixart.messenger.utils.GeoHelper;
@ -64,6 +64,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
public static final String COUNTERPART = "counterpart";
public static final String TRUE_COUNTERPART = "trueCounterpart";
public static final String BODY = "body";
public static final String BODY_LANGUAGE = "bodyLanguage";
public static final String TIME_SENT = "timeSent";
public static final String ENCRYPTION = "encryption";
public static final String STATUS = "status";
@ -83,6 +84,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
public static final String FILE_DELETED = "file_deleted";
public static final String ME_COMMAND = "/me";
public static final String ERROR_MESSAGE_CANCELLED = "de.pixart.messenger.cancelled";
public static final String DELETED_MESSAGE_BODY = "de.pixart.messenger.message_deleted";
public boolean markable = false;
protected String conversationUuid;
@ -97,11 +99,12 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
protected boolean file_deleted = false;
protected boolean carbon = false;
protected boolean oob = false;
protected String edited = null;
protected List<Edit> edits = new ArrayList<>();
protected String relativeFilePath;
protected boolean read = true;
protected boolean deleted = false;
protected String remoteMsgId = null;
private String bodyLanguage = null;
protected String serverMsgId = null;
private final Conversational conversation;
protected Transferable transferable = null;
@ -150,7 +153,8 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
null,
null,
false,
false);
false,
null);
}
protected Message(final Conversational conversation, final String uuid, final String conversationUUid, final Jid counterpart,
@ -159,7 +163,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
final String remoteMsgId, final String relativeFilePath,
final String serverMsgId, final String fingerprint, final boolean read, final boolean deleted,
final String edited, final boolean oob, final String errorMessage, final Set<ReadByMarker> readByMarkers,
final boolean markable, final boolean file_deleted) {
final boolean markable, final boolean file_deleted, final String bodyLanguage) {
this.conversation = conversation;
this.uuid = uuid;
this.conversationUuid = conversationUUid;
@ -177,12 +181,13 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
this.axolotlFingerprint = fingerprint;
this.read = read;
this.deleted = deleted;
this.edited = edited;
this.edits = Edit.fromJson(edited);
this.oob = oob;
this.errorMessage = errorMessage;
this.readByMarkers = readByMarkers == null ? new HashSet<ReadByMarker>() : readByMarkers;
this.readByMarkers = readByMarkers == null ? new HashSet<>() : readByMarkers;
this.markable = markable;
this.file_deleted = file_deleted;
this.bodyLanguage = bodyLanguage;
}
public static Message fromCursor(Cursor cursor, Conversation conversation) {
@ -208,7 +213,9 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
cursor.getString(cursor.getColumnIndex(ERROR_MESSAGE)),
ReadByMarker.fromJsonString(cursor.getString(cursor.getColumnIndex(READ_BY_MARKERS))),
cursor.getInt(cursor.getColumnIndex(MARKABLE)) > 0,
cursor.getInt(cursor.getColumnIndex(FILE_DELETED)) > 0);
cursor.getInt(cursor.getColumnIndex(FILE_DELETED)) > 0,
cursor.getString(cursor.getColumnIndex(BODY_LANGUAGE))
);
}
private static Jid fromString(String value) {
@ -264,12 +271,17 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
values.put(FINGERPRINT, axolotlFingerprint);
values.put(READ, read ? 1 : 0);
values.put(DELETED, deleted ? 1 : 0);
values.put(EDITED, edited);
try {
values.put(EDITED, Edit.toJson(edits));
} catch (JSONException e) {
Log.e(Config.LOGTAG, "error persisting json for edits", e);
}
values.put(OOB, oob ? 1 : 0);
values.put(ERROR_MESSAGE, errorMessage);
values.put(READ_BY_MARKERS, ReadByMarker.toJson(readByMarkers).toString());
values.put(MARKABLE, markable ? 1 : 0);
values.put(FILE_DELETED, file_deleted ? 1 : 0);
values.put(BODY_LANGUAGE, bodyLanguage);
return values;
}
@ -392,6 +404,10 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
return this.deleted;
}
public void setMessageDeleted(boolean deleted) {
this.deleted = deleted;
}
public boolean isFileDeleted() {
return this.file_deleted;
}
@ -436,12 +452,32 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
this.carbon = carbon;
}
public void setEdited(String edited) {
this.edited = edited;
public void putEdited(String edited, String serverMsgId) {
final Edit edit = new Edit(edited, serverMsgId);
if (this.edits.size() < 128 && !this.edits.contains(edit)) {
this.edits.add(edit);
}
}
boolean remoteMsgIdMatchInEdit(String id) {
for (Edit edit : this.edits) {
if (id.equals(edit.getEditedId())) {
return true;
}
}
return false;
}
public String getBodyLanguage() {
return this.bodyLanguage;
}
public void setBodyLanguage(String language) {
this.bodyLanguage = language;
}
public boolean edited() {
return this.edited != null;
return this.edits.size() > 0;
}
public void setTrueCounterpart(Jid trueCounterpart) {
@ -495,7 +531,9 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
boolean similar(Message message) {
if (!isPrivateMessage() && this.serverMsgId != null && message.getServerMsgId() != null) {
return this.serverMsgId.equals(message.getServerMsgId());
return this.serverMsgId.equals(message.getServerMsgId()) || Edit.wasPreviouslyEditedServerMsgId(edits, message.getServerMsgId());
} else if (Edit.wasPreviouslyEditedServerMsgId(edits, message.getServerMsgId())) {
return true;
} else if (this.body == null || this.counterpart == null) {
return false;
} else {
@ -510,7 +548,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
final boolean matchingCounterpart = this.counterpart.equals(message.getCounterpart());
if (message.getRemoteMsgId() != null) {
final boolean hasUuid = CryptoHelper.UUID_PATTERN.matcher(message.getRemoteMsgId()).matches();
if (hasUuid && this.edited != null && matchingCounterpart && this.edited.equals(message.getRemoteMsgId())) {
if (hasUuid && matchingCounterpart && Edit.wasPreviouslyEditedRemoteMsgId(edits, message.getRemoteMsgId())) {
return true;
}
return (message.getRemoteMsgId().equals(this.remoteMsgId) || message.getRemoteMsgId().equals(this.uuid))
@ -592,12 +630,15 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
this.getCounterpart() != null &&
this.getCounterpart().equals(message.getCounterpart()) &&
this.edited() == message.edited() &&
!this.isMessageDeleted() == !message.isMessageDeleted() &&
(message.getTimeSent() - this.getTimeSent()) <= (Config.MESSAGE_MERGE_WINDOW * 1000) &&
this.getBody().length() + message.getBody().length() <= Config.MAX_DISPLAY_MESSAGE_CHARS &&
!message.isGeoUri() &&
!this.isGeoUri() &&
!message.isWebUri() &&
!this.isWebUri() &&
!message.isOOb() &&
!this.isOOb() &&
!message.treatAsDownloadable() &&
!this.treatAsDownloadable() &&
!message.getBody().startsWith(ME_COMMAND) &&
@ -640,6 +681,10 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
}
}
public boolean isOOb() {
return oob;
}
public static class MergeSeparator {
}
@ -723,7 +768,19 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
}
public String getEditedId() {
return edited;
if (edits.size() > 0) {
return edits.get(edits.size() - 1).getEditedId();
} else {
throw new IllegalStateException("Attempting to store unedited message");
}
}
public String getEditedIdWireFormat() {
if (edits.size() > 0) {
return edits.get(Config.USE_LMC_VERSION_1_1 ? 0 : edits.size() - 1).getEditedId();
} else {
throw new IllegalStateException("Attempting to store unedited message");
}
}
public void setOob(boolean isOob) {
@ -792,7 +849,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
if (this.transferable != null) {
fileParams.size = this.transferable.getFileSize();
}
String parts[] = body == null ? new String[0] : body.split("\\|");
final String[] parts = body == null ? new String[0] : body.split("\\|");
switch (parts.length) {
case 1:
try {
@ -818,6 +875,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
case 6:
fileParams.url = parseUrl(parts[0]);
fileParams.size = parseLong(parts[1]);
fileParams.runtime = parseInt(parts[4]);
fileParams.subject = parseString(parts[5]);
break;

View file

@ -1,10 +1,11 @@
package de.pixart.messenger.entities;
import android.annotation.SuppressLint;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
@ -95,7 +96,7 @@ public class MucOptions {
public void resetChatState() {
synchronized (users) {
for (User user : users) {
user.chatState = Config.DEFAULT_CHATSTATE;
user.chatState = Config.DEFAULT_CHAT_STATE;
}
}
}
@ -335,7 +336,7 @@ public class MucOptions {
}
public boolean isContactInRoom(Contact contact) {
return findUserByRealJid(contact.getJid().asBareJid()) != null;
return contact != null && findUserByRealJid(contact.getJid().asBareJid()) != null;
}
public boolean isUserInRoom(Jid jid) {
@ -417,7 +418,7 @@ public class MucOptions {
}
}
private String getProposedNick() {
public String getProposedNick() {
final Bookmark bookmark = this.conversation.getBookmark();
final String bookmarkedNick = normalize(account.getJid(), bookmark == null ? null : bookmark.getNick());
if (bookmarkedNick != null) {
@ -426,12 +427,16 @@ public class MucOptions {
} else if (!conversation.getJid().isBareJid()) {
return conversation.getJid().getResource();
} else {
final String displayName = normalize(account.getJid(), account.getDisplayName());
if (displayName == null) {
return JidHelper.localPartOrFallback(account.getJid());
} else {
return displayName;
}
return defaultNick(account);
}
}
public static String defaultNick(final Account account) {
final String displayName = normalize(account.getJid(), account.getDisplayName());
if (displayName == null) {
return JidHelper.localPartOrFallback(account.getJid());
} else {
return displayName;
}
}
@ -742,7 +747,7 @@ public class MucOptions {
private long pgpKeyId = 0;
private Avatar avatar;
private MucOptions options;
private ChatState chatState = Config.DEFAULT_CHATSTATE;
private ChatState chatState = Config.DEFAULT_CHAT_STATE;
public User(MucOptions options, Jid fullJid) {
this.options = options;

View file

@ -1,6 +1,6 @@
package de.pixart.messenger.entities;
import android.support.annotation.NonNull;
import androidx.annotation.NonNull;
import java.util.Locale;

View file

@ -0,0 +1,92 @@
package de.pixart.messenger.entities;
import android.content.Context;
import android.text.TextUtils;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import de.pixart.messenger.utils.UIHelper;
import rocks.xmpp.addr.Jid;
public class RawBlockable implements ListItem, Blockable {
private final Account account;
private final Jid jid;
public RawBlockable(Account account, Jid jid) {
this.account = account;
this.jid = jid;
}
@Override
public boolean isBlocked() {
return true;
}
@Override
public boolean isDomainBlocked() {
throw new AssertionError("not implemented");
}
@Override
public Jid getBlockedJid() {
return this.jid;
}
@Override
public String getDisplayName() {
if (jid.isFullJid()) {
return jid.getResource();
} else {
return jid.toEscapedString();
}
}
@Override
public int getOffline() {
return 0;
}
@Override
public Jid getJid() {
return this.jid;
}
@Override
public List<Tag> getTags(Context context) {
return Collections.emptyList();
}
@Override
public boolean match(Context context, String needle) {
if (TextUtils.isEmpty(needle)) {
return true;
}
needle = needle.toLowerCase(Locale.US).trim();
String[] parts = needle.split("\\s+");
for (String part : parts) {
if (!jid.toEscapedString().contains(part)) {
return false;
}
}
return true;
}
@Override
public Account getAccount() {
return account;
}
@Override
public int getAvatarBackgroundColor() {
return UIHelper.getColorForName(jid.toEscapedString());
}
@Override
public int compareTo(ListItem o) {
return this.getDisplayName().compareToIgnoreCase(
o.getDisplayName());
}
}

View file

@ -111,12 +111,16 @@ public class ReadByMarker {
ReadByMarker marker = new ReadByMarker();
try {
marker.fullJid = Jid.of(jsonObject.getString("fullJid"));
} catch (JSONException | IllegalArgumentException e) {
} catch (JSONException e) {
marker.fullJid = null;
} catch (IllegalArgumentException e) {
marker.fullJid = null;
}
try {
marker.realJid = Jid.of(jsonObject.getString("realJid"));
} catch (JSONException | IllegalArgumentException e) {
} catch (JSONException e) {
marker.realJid = null;
} catch (IllegalArgumentException e) {
marker.realJid = null;
}
return marker;
@ -125,7 +129,9 @@ public class ReadByMarker {
public static Set<ReadByMarker> fromJsonString(String json) {
try {
return fromJson(new JSONArray(json));
} catch (JSONException | NullPointerException e) {
} catch (JSONException e) {
return new HashSet<>();
} catch (NullPointerException e) {
return new HashSet<>();
}
}

View file

@ -0,0 +1,88 @@
package de.pixart.messenger.entities;
import com.google.common.base.Objects;
import com.google.common.base.Strings;
import com.google.common.collect.ComparisonChain;
import de.pixart.messenger.services.AvatarService;
import de.pixart.messenger.utils.LanguageUtils;
import de.pixart.messenger.utils.UIHelper;
import rocks.xmpp.addr.Jid;
public class Room implements AvatarService.Avatarable, Comparable<Room> {
public String address;
public String name;
public String description;
public String language;
public int nusers;
public Room(String address, String name, String description, String language, int nusers) {
this.address = address;
this.name = name;
this.description = description;
this.language = language;
this.nusers = nusers;
}
public Room() {
}
public String getName() {
return name;
}
public String getDescription() {
return description;
}
public Jid getRoom() {
try {
return Jid.of(address);
} catch (IllegalArgumentException e) {
return null;
}
}
public String getLanguage() {
return LanguageUtils.convert(language);
}
@Override
public int getAvatarBackgroundColor() {
Jid room = getRoom();
return UIHelper.getColorForName(room != null ? room.asBareJid().toEscapedString() : name);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Room room = (Room) o;
return Objects.equal(address, room.address) &&
Objects.equal(name, room.name) &&
Objects.equal(description, room.description);
}
@Override
public int hashCode() {
return Objects.hashCode(address, name, description);
}
public boolean contains(String needle) {
return Strings.nullToEmpty(name).contains(needle)
|| Strings.nullToEmpty(description).contains(needle)
|| Strings.nullToEmpty(address).contains(needle);
}
@Override
public int compareTo(Room o) {
return ComparisonChain.start()
.compare(o.nusers, nusers)
.compare(Strings.nullToEmpty(name), Strings.nullToEmpty(o.name))
.compare(Strings.nullToEmpty(address), Strings.nullToEmpty(o.address))
.result();
}
}

View file

@ -2,9 +2,10 @@ package de.pixart.messenger.entities;
import android.content.ContentValues;
import android.database.Cursor;
import android.support.annotation.NonNull;
import android.util.Base64;
import androidx.annotation.NonNull;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@ -85,6 +86,18 @@ public class ServiceDiscoveryResult {
}
}
private ServiceDiscoveryResult() {
this.hash = "sha-1";
this.features = Collections.emptyList();
this.identities = Collections.emptyList();
this.ver = null;
this.forms = Collections.emptyList();
}
public static ServiceDiscoveryResult empty() {
return new ServiceDiscoveryResult();
}
public ServiceDiscoveryResult(Cursor cursor) throws JSONException {
this(
cursor.getString(cursor.getColumnIndex(HASH)),
@ -144,7 +157,7 @@ public class ServiceDiscoveryResult {
}
public String getVer() {
return new String(Base64.encode(this.ver, Base64.DEFAULT)).trim();
return Base64.encodeToString(this.ver, Base64.NO_WRAP);
}
public List<Identity> getIdentities() {

View file

@ -19,6 +19,7 @@ public interface Transferable {
"otr"
);
int STATUS_WAITING = 0x199;
int STATUS_UNKNOWN = 0x200;
int STATUS_CHECKING = 0x201;
int STATUS_FAILED = 0x202;
@ -26,6 +27,7 @@ public interface Transferable {
int STATUS_DOWNLOADING = 0x204;
int STATUS_OFFER_CHECK_FILESIZE = 0x206;
int STATUS_UPLOADING = 0x207;
int STATUS_CANCELLED = 0x208;
boolean start();

View file

@ -19,6 +19,7 @@ import de.pixart.messenger.entities.Account;
import de.pixart.messenger.services.XmppConnectionService;
import de.pixart.messenger.utils.Namespace;
import de.pixart.messenger.utils.PhoneHelper;
import de.pixart.messenger.xmpp.XmppConnection;
import de.pixart.messenger.xmpp.jingle.stanzas.Content;
public abstract class AbstractGenerator {
@ -29,6 +30,8 @@ public abstract class AbstractGenerator {
Content.Version.FT_5.getNamespace(),
Namespace.JINGLE_TRANSPORTS_S5B,
Namespace.JINGLE_TRANSPORTS_IBB,
Namespace.JINGLE_ENCRYPTED_TRANSPORT,
Namespace.JINGLE_ENCRYPTED_TRANSPORT_OMEMO,
"http://jabber.org/protocol/muc",
"jabber:x:conference",
Namespace.OOB,
@ -36,7 +39,6 @@ public abstract class AbstractGenerator {
"http://jabber.org/protocol/disco#info",
"urn:xmpp:avatar:metadata+notify",
Namespace.NICK + "+notify",
Namespace.BOOKMARKS + "+notify",
"urn:xmpp:ping",
"jabber:iq:version",
"http://jabber.org/protocol/chatstates"
@ -100,8 +102,8 @@ public abstract class AbstractGenerator {
for (String feature : getFeatures(account)) {
s.append(feature).append('<');
}
byte[] sha1 = md.digest(s.toString().getBytes());
return new String(Base64.encode(sha1, Base64.DEFAULT)).trim();
final byte[] sha1 = md.digest(s.toString().getBytes());
return Base64.encodeToString(sha1, Base64.NO_WRAP);
}
public static String getTimestamp(long time) {
@ -110,7 +112,8 @@ public abstract class AbstractGenerator {
}
public List<String> getFeatures(Account account) {
ArrayList<String> features = new ArrayList<>(Arrays.asList(FEATURES));
final XmppConnection connection = account.getXmppConnection();
final ArrayList<String> features = new ArrayList<>(Arrays.asList(FEATURES));
if (mXmppConnectionService.confirmMessages()) {
features.addAll(Arrays.asList(MESSAGE_CONFIRMATION_FEATURES));
}
@ -129,6 +132,11 @@ public abstract class AbstractGenerator {
if (mXmppConnectionService.broadcastLastActivity()) {
features.add(Namespace.IDLE);
}
if (connection != null && connection.getFeatures().bookmarks2()) {
features.add(Namespace.BOOKMARKS2 + "+notify");
} else {
features.add(Namespace.BOOKMARKS + "+notify");
}
Collections.sort(features);
return features;
}

View file

@ -24,6 +24,7 @@ import de.pixart.messenger.Config;
import de.pixart.messenger.R;
import de.pixart.messenger.crypto.axolotl.AxolotlService;
import de.pixart.messenger.entities.Account;
import de.pixart.messenger.entities.Bookmark;
import de.pixart.messenger.entities.Conversation;
import de.pixart.messenger.entities.DownloadableFile;
import de.pixart.messenger.services.MessageArchiveService;
@ -57,12 +58,6 @@ public class IqGenerator extends AbstractGenerator {
return packet;
}
public IqPacket purgeOfflineMessages() {
final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
packet.addChild("offline", Namespace.FLEXIBLE_OFFLINE_MESSAGE_RETRIEVAL).addChild("purge");
return packet;
}
public IqPacket versionResponse(final IqPacket request) {
final IqPacket packet = request.generateResponse(IqPacket.TYPE.RESULT);
Element query = packet.query("jabber:iq:version");
@ -96,6 +91,12 @@ public class IqGenerator extends AbstractGenerator {
return packet;
}
public IqPacket purgeOfflineMessages() {
final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
packet.addChild("offline", Namespace.FLEXIBLE_OFFLINE_MESSAGE_RETRIEVAL).addChild("purge");
return packet;
}
protected IqPacket publish(final String node, final Element item, final Bundle options) {
final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB);
@ -124,6 +125,10 @@ public class IqGenerator extends AbstractGenerator {
return packet;
}
public IqPacket retrieveBookmarks() {
return retrieve(Namespace.BOOKMARKS2, null);
}
public IqPacket publishNick(String nick) {
final Element item = new Element("item");
item.addChild("nick", Namespace.NICK).setContent(nick);
@ -137,6 +142,16 @@ public class IqGenerator extends AbstractGenerator {
return packet;
}
public IqPacket deleteItem(final String node, final String id) {
IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
final Element pubsub = packet.addChild("pubsub", Namespace.PUBSUB);
final Element retract = pubsub.addChild("retract");
retract.setAttribute("node", node);
retract.setAttribute("notify", "true");
retract.addChild("item").setAttribute("id", id);
return packet;
}
public IqPacket publishAvatar(Avatar avatar, Bundle options) {
final Element item = new Element("item");
item.setAttribute("id", avatar.sha1sum);
@ -145,9 +160,9 @@ public class IqGenerator extends AbstractGenerator {
return publish("urn:xmpp:avatar:data", item, options);
}
public IqPacket publishElement(final String namespace, final Element element, final Bundle options) {
public IqPacket publishElement(final String namespace, final Element element, String id, final Bundle options) {
final Element item = new Element("item");
item.setAttribute("id", "current");
item.setAttribute("id", id);
item.addChild(element);
return publish(namespace, item, options);
}
@ -221,6 +236,21 @@ public class IqGenerator extends AbstractGenerator {
return publish(AxolotlService.PEP_DEVICE_LIST, item, publishOptions);
}
public Element publishBookmarkItem(final Bookmark bookmark) {
final String name = bookmark.getBookmarkName();
final String nick = bookmark.getNick();
final boolean autojoin = bookmark.autojoin();
final Element conference = new Element("conference", Namespace.BOOKMARKS2);
if (name != null) {
conference.setAttribute("name", name);
}
if (nick != null) {
conference.addChild("nick").setContent(nick);
}
conference.setAttribute("autojoin", String.valueOf(autojoin));
return conference;
}
public IqPacket publishBundles(final SignedPreKeyRecord signedPreKeyRecord, final IdentityKey identityKey,
final Set<PreKeyRecord> preKeyRecords, final int deviceId, Bundle publishOptions) {
final Element item = new Element("item");
@ -229,17 +259,17 @@ public class IqGenerator extends AbstractGenerator {
final Element signedPreKeyPublic = bundle.addChild("signedPreKeyPublic");
signedPreKeyPublic.setAttribute("signedPreKeyId", signedPreKeyRecord.getId());
ECPublicKey publicKey = signedPreKeyRecord.getKeyPair().getPublicKey();
signedPreKeyPublic.setContent(Base64.encodeToString(publicKey.serialize(), Base64.DEFAULT));
signedPreKeyPublic.setContent(Base64.encodeToString(publicKey.serialize(), Base64.NO_WRAP));
final Element signedPreKeySignature = bundle.addChild("signedPreKeySignature");
signedPreKeySignature.setContent(Base64.encodeToString(signedPreKeyRecord.getSignature(), Base64.DEFAULT));
signedPreKeySignature.setContent(Base64.encodeToString(signedPreKeyRecord.getSignature(), Base64.NO_WRAP));
final Element identityKeyElement = bundle.addChild("identityKey");
identityKeyElement.setContent(Base64.encodeToString(identityKey.serialize(), Base64.DEFAULT));
identityKeyElement.setContent(Base64.encodeToString(identityKey.serialize(), Base64.NO_WRAP));
final Element prekeys = bundle.addChild("prekeys", AxolotlService.PEP_PREFIX);
for (PreKeyRecord preKeyRecord : preKeyRecords) {
final Element prekey = prekeys.addChild("preKeyPublic");
prekey.setAttribute("preKeyId", preKeyRecord.getId());
prekey.setContent(Base64.encodeToString(preKeyRecord.getKeyPair().getPublicKey().serialize(), Base64.DEFAULT));
prekey.setContent(Base64.encodeToString(preKeyRecord.getKeyPair().getPublicKey().serialize(), Base64.NO_WRAP));
}
return publish(AxolotlService.PEP_BUNDLES + ":" + deviceId, item, publishOptions);
@ -253,13 +283,13 @@ public class IqGenerator extends AbstractGenerator {
for (int i = 0; i < certificates.length; ++i) {
try {
Element certificate = chain.addChild("certificate");
certificate.setContent(Base64.encodeToString(certificates[i].getEncoded(), Base64.DEFAULT));
certificate.setContent(Base64.encodeToString(certificates[i].getEncoded(), Base64.NO_WRAP));
certificate.setAttribute("index", i);
} catch (CertificateEncodingException e) {
Log.d(Config.LOGTAG, "could not encode certificate");
}
}
verification.addChild("signature").setContent(Base64.encodeToString(signature, Base64.DEFAULT));
verification.addChild("signature").setContent(Base64.encodeToString(signature, Base64.NO_WRAP));
return publish(AxolotlService.PEP_VERIFICATION + ":" + deviceId, item);
}
@ -304,7 +334,7 @@ public class IqGenerator extends AbstractGenerator {
public IqPacket generateSetBlockRequest(final Jid jid, boolean reportSpam) {
final IqPacket iq = new IqPacket(IqPacket.TYPE.SET);
final Element block = iq.addChild("block", Namespace.BLOCKING);
final Element item = block.addChild("item").setAttribute("jid", jid.asBareJid().toString());
final Element item = block.addChild("item").setAttribute("jid", jid.toEscapedString());
if (reportSpam) {
item.addChild("report", "urn:xmpp:reporting:0").addChild("spam");
}
@ -315,7 +345,7 @@ public class IqGenerator extends AbstractGenerator {
public IqPacket generateSetUnblockRequest(final Jid jid) {
final IqPacket iq = new IqPacket(IqPacket.TYPE.SET);
final Element block = iq.addChild("unblock", Namespace.BLOCKING);
block.addChild("item").setAttribute("jid", jid.asBareJid().toString());
block.addChild("item").setAttribute("jid", jid.toEscapedString());
return iq;
}
@ -433,14 +463,35 @@ public class IqGenerator extends AbstractGenerator {
}
public IqPacket pushTokenToAppServer(Jid appServer, String token, String deviceId) {
IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
return pushTokenToAppServer(appServer, token, deviceId, null);
}
public IqPacket pushTokenToAppServer(Jid appServer, String token, String deviceId, Jid muc) {
final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
packet.setTo(appServer);
Element command = packet.addChild("command", "http://jabber.org/protocol/commands");
final Element command = packet.addChild("command", Namespace.COMMANDS);
command.setAttribute("node", "register-push-fcm");
command.setAttribute("action", "execute");
Data data = new Data();
final Data data = new Data();
data.put("token", token);
data.put("android-id", deviceId);
if (muc != null) {
data.put("muc", muc.toEscapedString());
}
data.submit();
command.addChild(data);
return packet;
}
public IqPacket unregisterChannelOnAppServer(Jid appServer, String deviceId, String channel) {
final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
packet.setTo(appServer);
final Element command = packet.addChild("command", Namespace.COMMANDS);
command.setAttribute("node", "unregister-push-fcm");
command.setAttribute("action", "execute");
final Data data = new Data();
data.put("channel", channel);
data.put("android-id", deviceId);
data.submit();
command.addChild(data);
return packet;
@ -464,7 +515,7 @@ public class IqGenerator extends AbstractGenerator {
public IqPacket disablePush(final Jid jid, final String node) {
IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
Element disable = packet.addChild("disable", Namespace.PUSH);
disable.setAttribute("jid", jid.toString());
disable.setAttribute("jid", jid.toEscapedString());
disable.setAttribute("node", node);
return packet;
}
@ -522,4 +573,18 @@ public class IqGenerator extends AbstractGenerator {
}
return packet;
}
}
public IqPacket queryDiscoItems(Jid jid) {
IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
packet.setTo(jid);
packet.addChild("query", Namespace.DISCO_ITEMS);
return packet;
}
public IqPacket queryDiscoInfo(Jid jid) {
IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
packet.setTo(jid);
packet.addChild("query", Namespace.DISCO_INFO);
return packet;
}
}

View file

@ -14,7 +14,6 @@ import de.pixart.messenger.Config;
import de.pixart.messenger.crypto.axolotl.AxolotlService;
import de.pixart.messenger.crypto.axolotl.XmppAxolotlMessage;
import de.pixart.messenger.entities.Account;
import de.pixart.messenger.entities.Contact;
import de.pixart.messenger.entities.Conversation;
import de.pixart.messenger.entities.Message;
import de.pixart.messenger.http.P1S3UrlStreamHandler;
@ -63,7 +62,7 @@ public class MessageGenerator extends AbstractGenerator {
packet.setId(message.getUuid());
packet.addChild("origin-id", Namespace.STANZA_IDS).setAttribute("id", message.getUuid());
if (message.edited()) {
packet.addChild("replace", "urn:xmpp:message-correct:0").setAttribute("id", message.getEditedId());
packet.addChild("replace", "urn:xmpp:message-correct:0").setAttribute("id", message.getEditedIdWireFormat());
}
return packet;
}
@ -229,6 +228,10 @@ public class MessageGenerator extends AbstractGenerator {
if (password != null) {
x.setAttribute("password", password);
}
if (contact.isFullJid()) {
packet.addChild("no-store", "urn:xmpp:hints");
packet.addChild("no-copy", "urn:xmpp:hints");
}
return packet;
}

View file

@ -50,15 +50,20 @@ public class PresenceGenerator extends AbstractGenerator {
return selfPresence(account, status, true);
}
public PresencePacket selfPresence(Account account, Presence.Status status, boolean includePgpAnnouncement) {
PresencePacket packet = new PresencePacket();
if (status.toShowString() != null) {
packet.addChild("show").setContent(status.toShowString());
}
packet.setFrom(account.getJid());
final String sig = account.getPgpSignature();
if (includePgpAnnouncement && sig != null && mXmppConnectionService.getPgpEngine() != null) {
packet.addChild("x", "jabber:x:signed").setContent(sig);
public PresencePacket selfPresence(final Account account, final Presence.Status status, final boolean personal) {
final PresencePacket packet = new PresencePacket();
if (personal) {
final String sig = account.getPgpSignature();
final String message = account.getPresenceStatusMessage();
if (status.toShowString() != null) {
packet.addChild("show").setContent(status.toShowString());
}
if (!TextUtils.isEmpty(message)) {
packet.addChild(new Element("status").setContent(message));
}
if (sig != null && mXmppConnectionService.getPgpEngine() != null) {
packet.addChild("x", "jabber:x:signed").setContent(sig);
}
}
final String capHash = getCapHash(account);
if (capHash != null) {

View file

@ -104,7 +104,8 @@ public class HttpConnectionManager extends AbstractConnectionManager {
final SSLSocketFactory sf = new TLSSocketFactory(new X509TrustManager[]{trustManager}, mXmppConnectionService.getRNG());
connection.setSSLSocketFactory(sf);
connection.setHostnameVerifier(hostnameVerifier);
} catch (final KeyManagementException | NoSuchAlgorithmException ignored) {
} catch (final KeyManagementException ignored) {
} catch (final NoSuchAlgorithmException ignored) {
}
}
}

View file

@ -1,10 +1,14 @@
package de.pixart.messenger.http;
import android.os.PowerManager;
import android.support.annotation.Nullable;
import android.util.Log;
import androidx.annotation.Nullable;
import com.google.common.io.ByteStreams;
import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@ -36,16 +40,15 @@ import rocks.xmpp.addr.Jid;
public class HttpDownloadConnection implements Transferable {
private final Message message;
private final boolean mUseTor;
private HttpConnectionManager mHttpConnectionManager;
private XmppConnectionService mXmppConnectionService;
private URL mUrl;
private final Message message;
private DownloadableFile file;
private int mStatus = Transferable.STATUS_UNKNOWN;
private boolean acceptedAutomatically = false;
private int mProgress = 0;
private final boolean mUseTor;
private boolean canceled = false;
private Method method = Method.HTTP_UPLOAD;
@ -73,37 +76,51 @@ public class HttpDownloadConnection implements Transferable {
}
public void init(boolean interactive) {
if (message.isFileDeleted()) {
if (message.getType() == Message.TYPE_PRIVATE_FILE) {
message.setType(Message.TYPE_PRIVATE);
} else if (message.isFileOrImage()) {
message.setType(Message.TYPE_TEXT);
}
message.setOob(true);
message.setFileDeleted(false);
mXmppConnectionService.updateMessage(message);
}
this.message.setTransferable(this);
try {
final Message.FileParams fileParams = message.getFileParams();
if (message.hasFileOnRemoteHost()) {
mUrl = CryptoHelper.toHttpsUrl(message.getFileParams().url);
mUrl = CryptoHelper.toHttpsUrl(fileParams.url);
} else if (message.isOOb() && fileParams.url != null && fileParams.size > 0) {
mUrl = fileParams.url;
} else {
mUrl = CryptoHelper.toHttpsUrl(new URL(message.getBody().split("\n")[0]));
}
String[] parts = mUrl.getPath().toLowerCase().split("\\.");
String lastPart = parts.length >= 1 ? parts[parts.length - 1] : null;
String secondToLast = parts.length >= 2 ? parts[parts.length - 2] : null;
if ("pgp".equals(lastPart) || "gpg".equals(lastPart)) {
final AbstractConnectionManager.Extension extension = AbstractConnectionManager.Extension.of(mUrl.getPath());
if (VALID_CRYPTO_EXTENSIONS.contains(extension.main)) {
this.message.setEncryption(Message.ENCRYPTION_PGP);
} else if (message.getEncryption() != Message.ENCRYPTION_OTR
&& message.getEncryption() != Message.ENCRYPTION_AXOLOTL) {
this.message.setEncryption(Message.ENCRYPTION_NONE);
}
String extension;
if (VALID_CRYPTO_EXTENSIONS.contains(lastPart)) {
extension = secondToLast;
final String ext;
if (VALID_CRYPTO_EXTENSIONS.contains(extension.main)) {
ext = extension.secondary;
} else {
extension = lastPart;
ext = extension.main;
}
if (message.getStatus() == Message.STATUS_RECEIVED){
message.setRelativeFilePath(fileDateFormat.format(new Date(message.getTimeSent())) + "_" + message.getUuid().substring(0, 4) + (extension != null ? ("." + extension) : ""));
if (message.getStatus() == Message.STATUS_RECEIVED) {
message.setRelativeFilePath(fileDateFormat.format(new Date(message.getTimeSent())) + "_" + message.getUuid().substring(0, 4) + (ext != null ? ("." + ext) : ""));
} else {
message.setRelativeFilePath("Sent/" + fileDateFormat.format(new Date(message.getTimeSent())) + "_" + message.getUuid().substring(0, 4) + (extension != null ? ("." + extension) : ""));
message.setRelativeFilePath("Sent/" + fileDateFormat.format(new Date(message.getTimeSent())) + "_" + message.getUuid().substring(0, 4) + (ext != null ? ("." + ext) : ""));
}
this.file = mXmppConnectionService.getFileBackend().getFile(message, false);
final String reference = mUrl.getRef();
if (reference != null && AesGcmURLStreamHandler.IV_KEY.matcher(reference).matches()) {
this.file = new DownloadableFile(mXmppConnectionService.getCacheDir().getAbsolutePath() + "/" + message.getUuid());
this.file.setKeyAndIv(CryptoHelper.hexToBytes(reference));
Log.d(Config.LOGTAG, "create temporary OMEMO encrypted file: " + this.file.getAbsolutePath() + "(" + message.getMimeType() + ")");
} else {
this.file = mXmppConnectionService.getFileBackend().getFile(message, false);
}
if ((this.message.getEncryption() == Message.ENCRYPTION_OTR
@ -143,6 +160,33 @@ public class HttpDownloadConnection implements Transferable {
mHttpConnectionManager.updateConversationUi(true);
}
private void decryptFile() throws IOException {
final DownloadableFile outputFile = mXmppConnectionService.getFileBackend().getFile(message, true);
if (outputFile.getParentFile().mkdirs()) {
Log.d(Config.LOGTAG, "created parent directories for " + outputFile.getAbsolutePath());
}
if (!outputFile.createNewFile()) {
Log.w(Config.LOGTAG, "unable to create output file " + outputFile.getAbsolutePath());
}
final InputStream is = new FileInputStream(this.file);
outputFile.setKey(this.file.getKey());
outputFile.setIv(this.file.getIv());
final OutputStream os = AbstractConnectionManager.createOutputStream(outputFile, false, true);
ByteStreams.copy(is, os);
FileBackend.close(is);
FileBackend.close(os);
if (!file.delete()) {
Log.w(Config.LOGTAG, "unable to delete temporary OMEMO encrypted file " + file.getAbsolutePath());
}
}
private void finish() {
message.setTransferable(null);
mHttpConnectionManager.finishConnection(this);
@ -152,6 +196,7 @@ public class HttpDownloadConnection implements Transferable {
}
mHttpConnectionManager.updateConversationUi(true);
final boolean notifyAfterScan = notify;
final DownloadableFile file = mXmppConnectionService.getFileBackend().getFile(message, true);
mXmppConnectionService.getFileBackend().updateMediaScanner(file, () -> {
if (notifyAfterScan) {
mXmppConnectionService.getNotificationService().push(message);
@ -159,6 +204,12 @@ public class HttpDownloadConnection implements Transferable {
});
}
private void decryptIfNeeded() throws IOException {
if (file.getKey() != null && file.getIv() != null) {
decryptFile();
}
}
private void changeStatus(int status) {
this.mStatus = status;
mHttpConnectionManager.updateConversationUi(true);
@ -212,6 +263,7 @@ public class HttpDownloadConnection implements Transferable {
this.interactive = interactive;
}
@Override
public void run() {
if (mUrl.getProtocol().equalsIgnoreCase(P1S3UrlStreamHandler.PROTOCOL_NAME)) {
@ -265,6 +317,10 @@ public class HttpDownloadConnection implements Transferable {
retrieveFailed(e);
return;
}
final Message.FileParams fileParams = message.getFileParams();
FileBackend.updateFileParams(message, fileParams.url, size);
message.setOob(true);
mXmppConnectionService.databaseBackend.updateMessage(message, true);
file.setExpectedSize(size);
message.resetFileParams();
if (mHttpConnectionManager.hasStoragePermission()
@ -300,7 +356,6 @@ public class HttpDownloadConnection implements Transferable {
connection.setUseCaches(false);
Log.d(Config.LOGTAG, "url: " + connection.getURL().toString());
connection.setRequestProperty("User-Agent", mXmppConnectionService.getIqGenerator().getUserAgent());
connection.setRequestProperty("Accept-Encoding", "identity");
if (connection instanceof HttpsURLConnection) {
mHttpConnectionManager.setupTrustManager((HttpsURLConnection) connection, interactive);
}
@ -346,22 +401,26 @@ public class HttpDownloadConnection implements Transferable {
@Override
public void run() {
try {
changeStatus(STATUS_DOWNLOADING);
download();
updateImageBounds();
finish();
} catch (SSLHandshakeException e) {
changeStatus(STATUS_OFFER);
} catch (Exception e) {
if (interactive) {
showToastForException(e);
} else {
HttpDownloadConnection.this.acceptedAutomatically = false;
HttpDownloadConnection.this.mXmppConnectionService.getNotificationService().push(message);
changeStatus(STATUS_WAITING);
mXmppConnectionService.mDownloadExecutor.execute(() -> {
try {
changeStatus(STATUS_DOWNLOADING);
download();
decryptIfNeeded();
updateImageBounds();
finish();
} catch (SSLHandshakeException e) {
changeStatus(STATUS_OFFER);
} catch (Exception e) {
if (interactive) {
showToastForException(e);
} else {
HttpDownloadConnection.this.acceptedAutomatically = false;
HttpDownloadConnection.this.mXmppConnectionService.getNotificationService().push(message);
}
cancel();
}
cancel();
}
});
}
private void download() throws Exception {
@ -380,17 +439,17 @@ public class HttpDownloadConnection implements Transferable {
}
connection.setUseCaches(false);
connection.setRequestProperty("User-Agent", mXmppConnectionService.getIqGenerator().getUserAgent());
connection.setRequestProperty("Accept-Encoding", "identity");
final long expected = file.getExpectedSize();
final boolean tryResume = file.exists() && file.getKey() == null && file.getSize() > 0 && file.getSize() < expected;
final boolean tryResume = file.exists() && file.getSize() > 0 && file.getSize() < expected;
long resumeSize = 0;
if (tryResume) {
resumeSize = file.getSize();
Log.d(Config.LOGTAG, "http download trying resume after" + resumeSize + " of " + expected);
Log.d(Config.LOGTAG, "http download trying resume after " + resumeSize + " of " + expected);
connection.setRequestProperty("Range", "bytes=" + resumeSize + "-");
}
connection.setConnectTimeout(Config.SOCKET_TIMEOUT * 1000);
connection.setReadTimeout(Config.CONNECT_TIMEOUT * 1000);
connection.setReadTimeout(Config.SOCKET_TIMEOUT * 1000);
connection.connect();
is = new BufferedInputStream(connection.getInputStream());
final String contentRange = connection.getHeaderField("Content-Range");
@ -400,7 +459,7 @@ public class HttpDownloadConnection implements Transferable {
Log.d(Config.LOGTAG, "server resumed");
transmitted = file.getSize();
updateProgress(Math.round(((double) transmitted / expected) * 100));
os = AbstractConnectionManager.createAppendedOutputStream(file);
os = AbstractConnectionManager.createOutputStream(file, true, false);
if (os == null) {
throw new FileWriterException();
}
@ -408,7 +467,9 @@ public class HttpDownloadConnection implements Transferable {
long reportedContentLengthOnGet;
try {
reportedContentLengthOnGet = Long.parseLong(connection.getHeaderField("Content-Length"));
} catch (NumberFormatException | NullPointerException e) {
} catch (NumberFormatException e) {
reportedContentLengthOnGet = 0;
} catch (NullPointerException e) {
reportedContentLengthOnGet = 0;
}
if (expected != reportedContentLengthOnGet) {
@ -418,7 +479,7 @@ public class HttpDownloadConnection implements Transferable {
if (!file.exists() && !file.createNewFile()) {
throw new FileWriterException();
}
os = AbstractConnectionManager.createOutputStream(file, true);
os = AbstractConnectionManager.createOutputStream(file, false, false);
}
int count;
byte[] buffer = new byte[4096];
@ -439,8 +500,11 @@ public class HttpDownloadConnection implements Transferable {
} catch (IOException e) {
throw new FileWriterException();
}
} catch (CancellationException | IOException e) {
Log.d(Config.LOGTAG, "http download failed " + e.getMessage());
} catch (CancellationException e) {
Log.d(Config.LOGTAG, message.getConversation().getAccount().getJid().asBareJid() + ": http download canceled", e);
throw e;
} catch (IOException e) {
Log.d(Config.LOGTAG, message.getConversation().getAccount().getJid().asBareJid() + ": http download failed", e);
throw e;
} finally {
FileBackend.close(os);

View file

@ -47,6 +47,7 @@ public class HttpUploadConnection implements Transferable {
private String mime;
private SlotRequester.Slot slot;
private byte[] key = null;
private int mStatus = Transferable.STATUS_UNKNOWN;
private long transmitted = 0;
@ -66,7 +67,7 @@ public class HttpUploadConnection implements Transferable {
@Override
public int getStatus() {
return STATUS_UPLOADING;
return this.mStatus;
}
@Override
@ -105,11 +106,12 @@ public class HttpUploadConnection implements Transferable {
} else {
this.mime = this.file.getMimeType();
}
final long originalFileSize = file.getSize();
this.delayed = delay;
if (Config.ENCRYPT_ON_HTTP_UPLOADED
|| message.getEncryption() == Message.ENCRYPTION_AXOLOTL
|| message.getEncryption() == Message.ENCRYPTION_OTR) {
this.key = new byte[48];
this.key = new byte[44];
mXmppConnectionService.getRNG().nextBytes(this.key);
this.file.setKeyAndIv(this.key);
}
@ -128,14 +130,18 @@ public class HttpUploadConnection implements Transferable {
md5 = null;
}
this.file.setExpectedSize(file.getSize() + (file.getKey() != null ? 16 : 0));
this.file.setExpectedSize(originalFileSize + (file.getKey() != null ? 16 : 0));
message.resetFileParams();
this.mSlotRequester.request(method, account, file, mime, md5, new SlotRequester.OnSlotRequested() {
@Override
public void success(SlotRequester.Slot slot) {
if (!cancelled) {
HttpUploadConnection.this.slot = slot;
new Thread(HttpUploadConnection.this::upload).start();
changeStatus(STATUS_WAITING);
mXmppConnectionService.mUploadExecutor.execute(() -> {
changeStatus(STATUS_UPLOADING);
HttpUploadConnection.this.slot = slot;
HttpUploadConnection.this.upload();
});
}
}
@ -240,6 +246,11 @@ public class HttpUploadConnection implements Transferable {
}
}
private void changeStatus(int status) {
this.mStatus = status;
mHttpConnectionManager.updateConversationUi(true);
}
public Message getMessage() {
return message;
}

View file

@ -0,0 +1,419 @@
package de.pixart.messenger.http;
/*Copyright 2015 Bhavit Singh Sengar
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.*/
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.net.ssl.HandshakeCompletedListener;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
public class NoSSLv3SocketFactory extends SSLSocketFactory {
private final SSLSocketFactory delegate;
public NoSSLv3SocketFactory() {
this.delegate = HttpsURLConnection.getDefaultSSLSocketFactory();
}
public NoSSLv3SocketFactory(SSLSocketFactory delegate) {
this.delegate = delegate;
}
@Override
public String[] getDefaultCipherSuites() {
return delegate.getDefaultCipherSuites();
}
@Override
public String[] getSupportedCipherSuites() {
return delegate.getSupportedCipherSuites();
}
private Socket makeSocketSafe(Socket socket) {
if (socket instanceof SSLSocket) {
socket = new NoSSLv3SSLSocket((SSLSocket) socket);
}
return socket;
}
@Override
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
return makeSocketSafe(delegate.createSocket(s, host, port, autoClose));
}
@Override
public Socket createSocket(String host, int port) throws IOException {
return makeSocketSafe(delegate.createSocket(host, port));
}
@Override
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
return makeSocketSafe(delegate.createSocket(host, port, localHost, localPort));
}
@Override
public Socket createSocket(InetAddress host, int port) throws IOException {
return makeSocketSafe(delegate.createSocket(host, port));
}
@Override
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
return makeSocketSafe(delegate.createSocket(address, port, localAddress, localPort));
}
private class NoSSLv3SSLSocket extends DelegateSSLSocket {
private NoSSLv3SSLSocket(SSLSocket delegate) {
super(delegate);
}
@Override
public void setEnabledProtocols(String[] protocols) {
if (protocols != null && protocols.length == 1 && "SSLv3".equals(protocols[0])) {
List<String> enabledProtocols = new ArrayList<String>(Arrays.asList(delegate.getEnabledProtocols()));
if (enabledProtocols.size() > 1) {
enabledProtocols.remove("SSLv3");
System.out.println("Removed SSLv3 from enabled protocols");
} else {
System.out.println("SSL stuck with protocol available for " + String.valueOf(enabledProtocols));
}
protocols = enabledProtocols.toArray(new String[enabledProtocols.size()]);
}
super.setEnabledProtocols(protocols);
}
}
public class DelegateSSLSocket extends SSLSocket {
protected final SSLSocket delegate;
DelegateSSLSocket(SSLSocket delegate) {
this.delegate = delegate;
}
@Override
public String[] getSupportedCipherSuites() {
return delegate.getSupportedCipherSuites();
}
@Override
public String[] getEnabledCipherSuites() {
return delegate.getEnabledCipherSuites();
}
@Override
public void setEnabledCipherSuites(String[] suites) {
delegate.setEnabledCipherSuites(suites);
}
@Override
public String[] getSupportedProtocols() {
return delegate.getSupportedProtocols();
}
@Override
public String[] getEnabledProtocols() {
return delegate.getEnabledProtocols();
}
@Override
public void setEnabledProtocols(String[] protocols) {
delegate.setEnabledProtocols(protocols);
}
@Override
public SSLSession getSession() {
return delegate.getSession();
}
@Override
public void addHandshakeCompletedListener(HandshakeCompletedListener listener) {
delegate.addHandshakeCompletedListener(listener);
}
@Override
public void removeHandshakeCompletedListener(HandshakeCompletedListener listener) {
delegate.removeHandshakeCompletedListener(listener);
}
@Override
public void startHandshake() throws IOException {
delegate.startHandshake();
}
@Override
public void setUseClientMode(boolean mode) {
delegate.setUseClientMode(mode);
}
@Override
public boolean getUseClientMode() {
return delegate.getUseClientMode();
}
@Override
public void setNeedClientAuth(boolean need) {
delegate.setNeedClientAuth(need);
}
@Override
public void setWantClientAuth(boolean want) {
delegate.setWantClientAuth(want);
}
@Override
public boolean getNeedClientAuth() {
return delegate.getNeedClientAuth();
}
@Override
public boolean getWantClientAuth() {
return delegate.getWantClientAuth();
}
@Override
public void setEnableSessionCreation(boolean flag) {
delegate.setEnableSessionCreation(flag);
}
@Override
public boolean getEnableSessionCreation() {
return delegate.getEnableSessionCreation();
}
@Override
public void bind(SocketAddress localAddr) throws IOException {
delegate.bind(localAddr);
}
@Override
public synchronized void close() throws IOException {
delegate.close();
}
@Override
public void connect(SocketAddress remoteAddr) throws IOException {
delegate.connect(remoteAddr);
}
@Override
public void connect(SocketAddress remoteAddr, int timeout) throws IOException {
delegate.connect(remoteAddr, timeout);
}
@Override
public SocketChannel getChannel() {
return delegate.getChannel();
}
@Override
public InetAddress getInetAddress() {
return delegate.getInetAddress();
}
@Override
public InputStream getInputStream() throws IOException {
return delegate.getInputStream();
}
@Override
public boolean getKeepAlive() throws SocketException {
return delegate.getKeepAlive();
}
@Override
public InetAddress getLocalAddress() {
return delegate.getLocalAddress();
}
@Override
public int getLocalPort() {
return delegate.getLocalPort();
}
@Override
public SocketAddress getLocalSocketAddress() {
return delegate.getLocalSocketAddress();
}
@Override
public boolean getOOBInline() throws SocketException {
return delegate.getOOBInline();
}
@Override
public OutputStream getOutputStream() throws IOException {
return delegate.getOutputStream();
}
@Override
public int getPort() {
return delegate.getPort();
}
@Override
public synchronized int getReceiveBufferSize() throws SocketException {
return delegate.getReceiveBufferSize();
}
@Override
public SocketAddress getRemoteSocketAddress() {
return delegate.getRemoteSocketAddress();
}
@Override
public boolean getReuseAddress() throws SocketException {
return delegate.getReuseAddress();
}
@Override
public synchronized int getSendBufferSize() throws SocketException {
return delegate.getSendBufferSize();
}
@Override
public int getSoLinger() throws SocketException {
return delegate.getSoLinger();
}
@Override
public synchronized int getSoTimeout() throws SocketException {
return delegate.getSoTimeout();
}
@Override
public boolean getTcpNoDelay() throws SocketException {
return delegate.getTcpNoDelay();
}
@Override
public int getTrafficClass() throws SocketException {
return delegate.getTrafficClass();
}
@Override
public boolean isBound() {
return delegate.isBound();
}
@Override
public boolean isClosed() {
return delegate.isClosed();
}
@Override
public boolean isConnected() {
return delegate.isConnected();
}
@Override
public boolean isInputShutdown() {
return delegate.isInputShutdown();
}
@Override
public boolean isOutputShutdown() {
return delegate.isOutputShutdown();
}
@Override
public void sendUrgentData(int value) throws IOException {
delegate.sendUrgentData(value);
}
@Override
public void setKeepAlive(boolean keepAlive) throws SocketException {
delegate.setKeepAlive(keepAlive);
}
@Override
public void setOOBInline(boolean oobinline) throws SocketException {
delegate.setOOBInline(oobinline);
}
@Override
public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) {
delegate.setPerformancePreferences(connectionTime, latency, bandwidth);
}
@Override
public synchronized void setReceiveBufferSize(int size) throws SocketException {
delegate.setReceiveBufferSize(size);
}
@Override
public void setReuseAddress(boolean reuse) throws SocketException {
delegate.setReuseAddress(reuse);
}
@Override
public synchronized void setSendBufferSize(int size) throws SocketException {
delegate.setSendBufferSize(size);
}
@Override
public void setSoLinger(boolean on, int timeout) throws SocketException {
delegate.setSoLinger(on, timeout);
}
@Override
public synchronized void setSoTimeout(int timeout) throws SocketException {
delegate.setSoTimeout(timeout);
}
@Override
public void setTcpNoDelay(boolean on) throws SocketException {
delegate.setTcpNoDelay(on);
}
@Override
public void setTrafficClass(int value) throws SocketException {
delegate.setTrafficClass(value);
}
@Override
public void shutdownInput() throws IOException {
delegate.shutdownInput();
}
@Override
public void shutdownOutput() throws IOException {
delegate.shutdownOutput();
}
@Override
public String toString() {
return delegate.toString();
}
@Override
public boolean equals(Object o) {
return delegate.equals(o);
}
}
}

View file

@ -1,20 +1,15 @@
package de.pixart.messenger.http.services;
import com.google.common.base.Objects;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import de.pixart.messenger.services.AvatarService;
import de.pixart.messenger.utils.LanguageUtils;
import de.pixart.messenger.utils.UIHelper;
import de.pixart.messenger.entities.Room;
import retrofit2.Call;
import retrofit2.http.Body;
import retrofit2.http.GET;
import retrofit2.http.POST;
import retrofit2.http.Query;
import rocks.xmpp.addr.Jid;
public interface MuclumbusService {
@ -31,57 +26,8 @@ public interface MuclumbusService {
public List<Room> items;
}
class Room implements AvatarService.Avatarable {
public String address;
public String name;
public String description;
public String language;
public String getName() {
return name;
}
public String getDescription() {
return description;
}
public Jid getRoom() {
try {
return Jid.of(address);
} catch (IllegalArgumentException e) {
return null;
}
}
public String getLanguage() {
return LanguageUtils.convert(language);
}
@Override
public int getAvatarBackgroundColor() {
Jid room = getRoom();
return UIHelper.getColorForName(room != null ? room.asBareJid().toEscapedString() : name);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Room room = (Room) o;
return Objects.equal(address, room.address) &&
Objects.equal(name, room.name) &&
Objects.equal(description, room.description);
}
@Override
public int hashCode() {
return Objects.hashCode(address, name, description);
}
}
class SearchRequest {
public Set<String> keywords;
public final Set<String> keywords;
public SearchRequest(String keyword) {
this.keywords = Collections.singleton(keyword);

View file

@ -1,10 +1,12 @@
package de.pixart.messenger.parser;
import android.support.annotation.NonNull;
import android.text.TextUtils;
import android.util.Base64;
import android.util.Log;
import android.util.Pair;
import androidx.annotation.NonNull;
import org.whispersystems.libsignal.IdentityKey;
import org.whispersystems.libsignal.ecc.Curve;
import org.whispersystems.libsignal.ecc.ECPublicKey;
@ -27,12 +29,14 @@ import de.pixart.messenger.crypto.axolotl.AxolotlService;
import de.pixart.messenger.entities.Account;
import de.pixart.messenger.entities.Contact;
import de.pixart.messenger.entities.Conversation;
import de.pixart.messenger.entities.Room;
import de.pixart.messenger.services.XmppConnectionService;
import de.pixart.messenger.utils.Namespace;
import de.pixart.messenger.xml.Element;
import de.pixart.messenger.xmpp.InvalidJid;
import de.pixart.messenger.xmpp.OnIqPacketReceived;
import de.pixart.messenger.xmpp.OnUpdateBlocklist;
import de.pixart.messenger.xmpp.forms.Data;
import de.pixart.messenger.xmpp.stanzas.IqPacket;
import rocks.xmpp.addr.Jid;
@ -416,4 +420,55 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
}
}
}
public static List<Jid> items(IqPacket packet) {
ArrayList<Jid> items = new ArrayList<>();
final Element query = packet.findChild("query", Namespace.DISCO_ITEMS);
if (query == null) {
return items;
}
for (Element child : query.getChildren()) {
if ("item".equals(child.getName())) {
Jid jid = child.getAttributeAsJid("jid");
if (jid != null) {
items.add(jid);
}
}
}
return items;
}
public static Room parseRoom(IqPacket packet) {
final Element query = packet.findChild("query", Namespace.DISCO_INFO);
if (query == null) {
return null;
}
final Element x = query.findChild("x");
if (x == null) {
return null;
}
final Element identity = query.findChild("identity");
Data data = Data.parse(x);
String address = packet.getFrom().toEscapedString();
String name = identity == null ? null : identity.getAttribute("name");
String roomName = data.getValue("muc#roomconfig_roomname");
;
String description = data.getValue("muc#roominfo_description");
String language = data.getValue("muc#roominfo_lang");
String occupants = data.getValue("muc#roominfo_occupants");
int nusers;
try {
nusers = occupants == null ? 0 : Integer.parseInt(occupants);
} catch (NumberFormatException e) {
nusers = 0;
}
return new Room(
address,
TextUtils.isEmpty(roomName) ? name : roomName,
description,
language,
nusers
);
}
}

View file

@ -16,6 +16,7 @@ import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
@ -25,8 +26,10 @@ import de.pixart.messenger.crypto.OtrService;
import de.pixart.messenger.crypto.axolotl.AxolotlService;
import de.pixart.messenger.crypto.axolotl.BrokenSessionException;
import de.pixart.messenger.crypto.axolotl.NotEncryptedForThisDeviceException;
import de.pixart.messenger.crypto.axolotl.OutdatedSenderException;
import de.pixart.messenger.crypto.axolotl.XmppAxolotlMessage;
import de.pixart.messenger.entities.Account;
import de.pixart.messenger.entities.Bookmark;
import de.pixart.messenger.entities.Contact;
import de.pixart.messenger.entities.Conversation;
import de.pixart.messenger.entities.Conversational;
@ -43,6 +46,7 @@ import de.pixart.messenger.services.XmppConnectionService;
import de.pixart.messenger.utils.CryptoHelper;
import de.pixart.messenger.utils.Namespace;
import de.pixart.messenger.xml.Element;
import de.pixart.messenger.xml.LocalizedContent;
import de.pixart.messenger.xmpp.InvalidJid;
import de.pixart.messenger.xmpp.OnMessagePacketReceived;
import de.pixart.messenger.xmpp.chatstate.ChatState;
@ -50,6 +54,8 @@ import de.pixart.messenger.xmpp.pep.Avatar;
import de.pixart.messenger.xmpp.stanzas.MessagePacket;
import rocks.xmpp.addr.Jid;
import static de.pixart.messenger.entities.Message.DELETED_MESSAGE_BODY;
public class MessageParser extends AbstractParser implements OnMessagePacketReceived {
private static final List<String> CLIENTS_SENDING_HTML_IN_OTR = Arrays.asList("Pidgin", "Adium", "Trillian");
@ -202,7 +208,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
}
}
private Message parseAxolotlChat(Element axolotlMessage, Jid from, Conversation conversation, int status, boolean checkedForDuplicates, boolean postpone) {
private Message parseAxolotlChat(Element axolotlMessage, Jid from, Conversation conversation, int status, final boolean checkedForDuplicates, boolean postpone) {
final AxolotlService service = conversation.getAccount().getAxolotlService();
final XmppAxolotlMessage xmppAxolotlMessage;
try {
@ -217,14 +223,21 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
plaintextMessage = service.processReceivingPayloadMessage(xmppAxolotlMessage, postpone);
} catch (BrokenSessionException e) {
if (checkedForDuplicates) {
service.reportBrokenSessionException(e, postpone);
return new Message(conversation, "", Message.ENCRYPTION_AXOLOTL_FAILED, status);
if (service.trustedOrPreviouslyResponded(from.asBareJid())) {
service.reportBrokenSessionException(e, postpone);
return new Message(conversation, "", Message.ENCRYPTION_AXOLOTL_FAILED, status);
} else {
Log.d(Config.LOGTAG, "ignoring broken session exception because contact was not trusted");
return new Message(conversation, "", Message.ENCRYPTION_AXOLOTL_FAILED, status);
}
} else {
Log.d(Config.LOGTAG, "ignoring broken session exception because checkForDuplicase failed");
Log.d(Config.LOGTAG, "ignoring broken session exception because checkForDuplicates failed");
return null;
}
} catch (NotEncryptedForThisDeviceException e) {
return new Message(conversation, "", Message.ENCRYPTION_AXOLOTL_NOT_FOR_THIS_DEVICE, status);
} catch (OutdatedSenderException e) {
return new Message(conversation, "", Message.ENCRYPTION_AXOLOTL_FAILED, status);
}
if (plaintextMessage != null) {
Message finishedMessage = new Message(conversation, plaintextMessage.getPlaintext(), Message.ENCRYPTION_AXOLOTL, status);
@ -239,31 +252,28 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
return null;
}
private Invite extractInvite(Account account, Element message) {
Element x = message.findChild("x", "http://jabber.org/protocol/muc#user");
if (x != null) {
Element invite = x.findChild("invite");
private Invite extractInvite(Element message) {
final Element mucUser = message.findChild("x", Namespace.MUC_USER);
if (mucUser != null) {
Element invite = mucUser.findChild("invite");
if (invite != null) {
String password = x.findChildContent("password");
String password = mucUser.findChildContent("password");
Jid from = InvalidJid.getNullForInvalid(invite.getAttributeAsJid("from"));
Contact contact = from == null ? null : account.getRoster().getContact(from);
Jid room = InvalidJid.getNullForInvalid(message.getAttributeAsJid("from"));
if (room == null) {
return null;
}
return new Invite(room, password, contact);
return new Invite(room, password, false, from);
}
} else {
x = message.findChild("x", "jabber:x:conference");
if (x != null) {
Jid from = InvalidJid.getNullForInvalid(message.getAttributeAsJid("from"));
Contact contact = from == null ? null : account.getRoster().getContact(from);
Jid room = InvalidJid.getNullForInvalid(x.getAttributeAsJid("jid"));
if (room == null) {
return null;
}
return new Invite(room, x.getAttribute("password"), contact);
}
final Element conference = message.findChild("x", "jabber:x:conference");
if (conference != null) {
Jid from = InvalidJid.getNullForInvalid(message.getAttributeAsJid("from"));
Jid room = InvalidJid.getNullForInvalid(conference.getAttributeAsJid("jid"));
if (room == null) {
return null;
}
return new Invite(room, conference.getAttribute("password"), true, from);
}
return null;
}
@ -313,25 +323,55 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
if (account.getXmppConnection().getFeatures().bookmarksConversion()) {
final Element i = items.findChild("item");
final Element storage = i == null ? null : i.findChild("storage", Namespace.BOOKMARKS);
new Thread(() -> {
mXmppConnectionService.processBookmarks(account, storage, true);
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": processing bookmark PEP event");
}).start();
Map<Jid, Bookmark> bookmarks = Bookmark.parseFromStorage(storage, account);
mXmppConnectionService.processBookmarksInitial(account, bookmarks, true);
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": processing bookmark PEP event");
} else {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": ignoring bookmark PEP event because bookmark conversion was not detected");
}
} else if (Namespace.BOOKMARKS2.equals(node) && account.getJid().asBareJid().equals(from)) {
final Element item = items.findChild("item");
final Element retract = items.findChild("retract");
if (item != null) {
final Bookmark bookmark = Bookmark.parseFromItem(item, account);
if (bookmark != null) {
account.putBookmark(bookmark);
mXmppConnectionService.processModifiedBookmark(bookmark);
mXmppConnectionService.updateConversationUi();
}
}
if (retract != null) {
final Jid id = InvalidJid.getNullForInvalid(retract.getAttributeAsJid("id"));
if (id != null) {
account.removeBookmark(id);
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": deleted bookmark for " + id);
mXmppConnectionService.processDeletedBookmark(account, id);
mXmppConnectionService.updateConversationUi();
}
}
} else {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + " received pubsub notification for node=" + node);
}
}
private void parseDeleteEvent(final Element event, final Jid from, final Account account) {
final Element delete = event.findChild("delete");
if (delete == null) {
return;
}
String node = delete.getAttribute("node");
final String node = delete == null ? null : delete.getAttribute("node");
if (Namespace.NICK.equals(node)) {
Log.d(Config.LOGTAG, "parsing nick delete event from " + from);
setNick(account, from, null);
} else if (Namespace.BOOKMARKS2.equals(node) && account.getJid().asBareJid().equals(from)) {
account.setBookmarks(Collections.emptyMap());
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": deleted bookmarks node");
}
}
private void parsePurgeEvent(final Element event, final Jid from, final Account account) {
final Element purge = event.findChild("purge");
final String node = purge == null ? null : purge.getAttribute("node");
if (Namespace.BOOKMARKS2.equals(node) && account.getJid().asBareJid().equals(from)) {
account.setBookmarks(Collections.emptyMap());
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": purged bookmarks");
}
}
@ -358,12 +398,12 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
Message.STATUS_SEND_FAILED,
extractErrorMessage(packet));
final Element error = packet.findChild("error");
final boolean notAcceptable = error != null && error.hasChild("not-acceptable");
if (notAcceptable) {
final boolean pingWorthyError = error != null && (error.hasChild("not-acceptable") || error.hasChild("remote-server-timeout") || error.hasChild("remote-server-not-found"));
if (pingWorthyError) {
Conversation conversation = mXmppConnectionService.find(account, from);
if (conversation != null && conversation.getMode() == Conversational.MODE_MULTI) {
if (conversation.getMucOptions().online()) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received not-acceptable error for seemingly online muc at " + from);
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received ping worthy error for seemingly online muc at " + from);
mXmppConnectionService.mucSelfPingAndRejoin(conversation);
}
}
@ -429,8 +469,8 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
if (timestamp == null) {
timestamp = AbstractParser.parseTimestamp(original, AbstractParser.parseTimestamp(packet));
}
final String body = packet.getBody();
final Element mucUserElement = packet.findChild("x", "http://jabber.org/protocol/muc#user");
final LocalizedContent body = packet.getBody();
final Element mucUserElement = packet.findChild("x", Namespace.MUC_USER);
final String pgpEncrypted = packet.findChildContent("x", "jabber:x:encrypted");
final Element replaceElement = packet.findChild("replace", "urn:xmpp:message-correct:0");
final Element oob = packet.findChild("x", Namespace.OOB);
@ -438,7 +478,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
final URL xP1S3url = xP1S3 == null ? null : P1S3UrlStreamHandler.of(xP1S3);
final String oobUrl = oob != null ? oob.findChildContent("url") : null;
final String replacementId = replaceElement == null ? null : replaceElement.getAttribute("id");
final Element axolotlEncrypted = packet.findChild(XmppAxolotlMessage.CONTAINERTAG, AxolotlService.PEP_PREFIX);
final Element axolotlEncrypted = packet.findChildEnsureSingle(XmppAxolotlMessage.CONTAINERTAG, AxolotlService.PEP_PREFIX);
int status;
final Jid counterpart;
final Jid to = packet.getTo();
@ -479,9 +519,16 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
selfAddressed = false;
}
Invite invite = extractInvite(account, packet);
if (invite != null && invite.execute(account)) {
return;
final Invite invite = extractInvite(packet);
if (invite != null) {
if (isTypeGroupChat) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": ignoring invite to " + invite.jid + " because type=groupchat");
} else if (invite.direct && (mucUserElement != null || invite.inviter == null || mXmppConnectionService.isMuc(account, invite.inviter))) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": ignoring direct invite to " + invite.jid + " because it was received in MUC");
} else {
invite.execute(account);
return;
}
}
if ((body != null || pgpEncrypted != null || (axolotlEncrypted != null && axolotlEncrypted.hasChild("payload")) || oobUrl != null || xP1S3 != null) && !isMucStatusMessage) {
@ -504,13 +551,17 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
if (conversation.getMucOptions().isSelf(counterpart)) {
status = Message.STATUS_SEND_RECEIVED;
isCarbon = true; //not really carbon but received from another resource
//TODO this would be the place to change the body after something like mod_pastebin
if (mXmppConnectionService.markMessage(conversation, remoteMsgId, status, serverMsgId)) {
return;
} else if (remoteMsgId == null || Config.IGNORE_ID_REWRITE_IN_MUC) {
Message message = conversation.findSentMessageWithBody(packet.getBody());
if (message != null) {
mXmppConnectionService.markMessage(message, status);
return;
LocalizedContent localizedBody = packet.getBody();
if (localizedBody != null) {
Message message = conversation.findSentMessageWithBody(localizedBody.content);
if (message != null) {
mXmppConnectionService.markMessage(message, status);
return;
}
}
}
} else {
@ -518,15 +569,18 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
}
}
final Message message;
if (body != null && body.startsWith("?OTR") && Config.supportOtr()) {
if (body != null && body.content.startsWith("?OTR") && Config.supportOtr()) {
if (!isForwarded && !isTypeGroupChat && isProperlyAddressed && !conversationMultiMode) {
message = parseOtrChat(body, from, remoteMsgId, conversation);
message = parseOtrChat(body.content, from, remoteMsgId, conversation);
if (message == null) {
return;
}
} else {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": ignoring OTR message from " + from + " isForwarded=" + Boolean.toString(isForwarded) + ", isProperlyAddressed=" + Boolean.valueOf(isProperlyAddressed));
message = new Message(conversation, body, Message.ENCRYPTION_NONE, status);
message = new Message(conversation, body.content, Message.ENCRYPTION_NONE, status);
if (body.count > 1) {
message.setBodyLanguage(body.language);
}
}
} else if (xP1S3url != null) {
message = new Message(conversation, xP1S3url.toString(), Message.ENCRYPTION_NONE, status);
@ -557,8 +611,8 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
fallbacksBySourceId = Collections.emptySet();
origin = from;
}
final boolean checkedForDuplicates = serverMsgId != null && remoteMsgId != null && !conversation.possibleDuplicate(serverMsgId, remoteMsgId);
final boolean liveMessage = query == null && !isTypeGroupChat && mucUserElement == null;
final boolean checkedForDuplicates = liveMessage || (serverMsgId != null && remoteMsgId != null && !conversation.possibleDuplicate(serverMsgId, remoteMsgId));
if (origin != null) {
message = parseAxolotlChat(axolotlEncrypted, origin, conversation, status, checkedForDuplicates, query != null);
@ -598,7 +652,10 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
message.setEncryption(Message.ENCRYPTION_DECRYPTED);
}
} else {
message = new Message(conversation, body, Message.ENCRYPTION_NONE, status);
message = new Message(conversation, body.content, Message.ENCRYPTION_NONE, status);
if (body.count > 1) {
message.setBodyLanguage(body.language);
}
}
message.setCounterpart(counterpart);
@ -606,7 +663,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
message.setServerMsgId(serverMsgId);
message.setCarbon(isCarbon);
message.setTime(timestamp);
if (body != null && body.equals(oobUrl)) {
if (body != null && body.content != null && body.content.equals(oobUrl)) {
message.setOob(true);
if (CryptoHelper.isPgpEncryptedUrl(oobUrl)) {
message.setEncryption(Message.ENCRYPTION_DECRYPTED);
@ -650,7 +707,8 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
final boolean fingerprintsMatch = replacedMessage.getFingerprint() == null
|| replacedMessage.getFingerprint().equals(message.getFingerprint());
final boolean trueCountersMatch = replacedMessage.getTrueCounterpart() != null
&& replacedMessage.getTrueCounterpart().equals(message.getTrueCounterpart());
&& message.getTrueCounterpart() != null
&& replacedMessage.getTrueCounterpart().asBareJid().equals(message.getTrueCounterpart().asBareJid());
final boolean mucUserMatches = query == null && replacedMessage.sameMucUser(message); //can not be checked when using mam
final boolean duplicate = conversation.hasDuplicateMessage(message);
if (fingerprintsMatch && (trueCountersMatch || !conversationMultiMode || mucUserMatches) && !duplicate) {
@ -659,7 +717,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
final String uuid = replacedMessage.getUuid();
replacedMessage.setUuid(UUID.randomUUID().toString());
replacedMessage.setBody(message.getBody());
replacedMessage.setEdited(replacedMessage.getRemoteMsgId());
replacedMessage.putEdited(replacedMessage.getRemoteMsgId(), replacedMessage.getServerMsgId());
replacedMessage.setRemoteMsgId(remoteMsgId);
if (replacedMessage.getServerMsgId() == null || message.getServerMsgId() != null) {
replacedMessage.setServerMsgId(message.getServerMsgId());
@ -689,6 +747,9 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received message correction but verification didn't check out");
}
}
} else if (replacementId != null && !mXmppConnectionService.allowMessageCorrection() && message.getBody().equals(DELETED_MESSAGE_BODY)) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": received deleted message but LMC is deactivated");
return;
}
long deletionDate = mXmppConnectionService.getAutomaticMessageDeletionDate();
@ -719,7 +780,7 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
} else {
serverMsgIdUpdated = false;
}
Log.d(Config.LOGTAG, "skipping duplicate message with " + message.getCounterpart() + ". serverMsgIdUpdated=" + Boolean.toString(serverMsgIdUpdated));
Log.d(Config.LOGTAG, "skipping duplicate message with " + message.getCounterpart() + ". serverMsgIdUpdated=" + serverMsgIdUpdated);
return;
}
}
@ -812,11 +873,11 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
mXmppConnectionService.updateConversationUi();
}
if (isTypeGroupChat) {
if (packet.hasChild("subject")) {
if (packet.hasChild("subject")) { //TODO usually we would want to check for lack of body; however some servers do set a body :(
if (conversation != null && conversation.getMode() == Conversation.MODE_MULTI) {
conversation.setHasMessagesLeftOnServer(conversation.countMessages() > 0);
String subject = packet.findInternationalizedChildContent("subject");
if (conversation.getMucOptions().setSubject(subject)) {
final LocalizedContent subject = packet.findInternationalizedChildContentInDefaultNamespace("subject");
if (subject != null && conversation.getMucOptions().setSubject(subject.content)) {
mXmppConnectionService.updateConversation(conversation);
}
mXmppConnectionService.updateConversationUi();
@ -889,6 +950,9 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
final Jid sender = InvalidJid.getNullForInvalid(displayed.getAttributeAsJid("sender"));
if (packet.fromAccount(account) && !selfAddressed) {
dismissNotification(account, counterpart, query);
if (query == null) {
activateGracePeriod(account);
}
} else if (isTypeGroupChat) {
Conversation conversation = mXmppConnectionService.find(account, counterpart.asBareJid());
if (conversation != null && id != null && sender != null) {
@ -902,9 +966,8 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
mXmppConnectionService.markRead(conversation);
}
} else if (!counterpart.isBareJid() && trueJid != null) {
ReadByMarker readByMarker = ReadByMarker.from(counterpart, trueJid);
final ReadByMarker readByMarker = ReadByMarker.from(counterpart, trueJid);
if (message.addReadByMarker(readByMarker)) {
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": added read by (" + readByMarker.getRealJid() + ") to message '" + message.getBody() + "'");
mXmppConnectionService.updateMessage(message, false);
updateReadMarker(account, from, id, selfAddressed, counterpart, query);
}
@ -933,6 +996,8 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
parseEvent(event, original.getFrom(), account);
} else if (event.hasChild("delete")) {
parseDeleteEvent(event, original.getFrom(), account);
} else if (event.hasChild("purge")) {
parsePurgeEvent(event, original.getFrom(), account);
}
}
@ -1000,11 +1065,13 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
private class Invite {
final Jid jid;
final String password;
final Contact inviter;
final boolean direct;
final Jid inviter;
Invite(Jid jid, String password, Contact inviter) {
Invite(Jid jid, String password, boolean direct, Jid inviter) {
this.jid = jid;
this.password = password;
this.direct = direct;
this.inviter = inviter;
}
@ -1017,7 +1084,8 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
} else {
conversation.getMucOptions().setPassword(password);
mXmppConnectionService.databaseBackend.updateConversation(conversation);
mXmppConnectionService.joinMuc(conversation, inviter != null && inviter.mutualPresenceSubscription());
final Contact contact = inviter != null ? account.getRoster().getContactFromContactList(inviter) : null;
mXmppConnectionService.joinMuc(conversation, contact != null && contact.mutualPresenceSubscription());
mXmppConnectionService.updateConversationUi();
}
return true;

View file

@ -59,7 +59,7 @@ public class PresenceParser extends AbstractParser implements
final Jid from = packet.getFrom();
if (!from.isBareJid()) {
final String type = packet.getAttribute("type");
final Element x = packet.findChild("x", "http://jabber.org/protocol/muc#user");
final Element x = packet.findChild("x", Namespace.MUC_USER);
Avatar avatar = Avatar.parsePresence(packet.findChild("x", "vcard-temp:x:update"));
final List<String> codes = getStatusCodes(x);
if (type == null) {
@ -191,7 +191,7 @@ public class PresenceParser extends AbstractParser implements
final Jid alternate;
if (gone != null) {
final XmppUri xmppUri = new XmppUri(gone);
if (xmppUri.isJidValid()) {
if (xmppUri.isValidJid()) {
alternate = xmppUri.getJid();
} else {
alternate = null;
@ -361,7 +361,7 @@ public class PresenceParser extends AbstractParser implements
@Override
public void onPresencePacketReceived(Account account, PresencePacket packet) {
if (packet.hasChild("x", "http://jabber.org/protocol/muc#user")) {
if (packet.hasChild("x", Namespace.MUC_USER)) {
this.parseConferencePresence(packet, account);
} else if (packet.hasChild("x", "http://jabber.org/protocol/muc")) {
this.parseConferencePresence(packet, account);

View file

@ -28,14 +28,11 @@ import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
@ -54,6 +51,7 @@ import de.pixart.messenger.entities.Roster;
import de.pixart.messenger.entities.ServiceDiscoveryResult;
import de.pixart.messenger.services.ShortcutService;
import de.pixart.messenger.utils.CryptoHelper;
import de.pixart.messenger.utils.CursorUtils;
import de.pixart.messenger.utils.FtsUtils;
import de.pixart.messenger.utils.Resolver;
import de.pixart.messenger.xmpp.InvalidJid;
@ -63,7 +61,7 @@ import rocks.xmpp.addr.Jid;
public class DatabaseBackend extends SQLiteOpenHelper {
public static final String DATABASE_NAME = "history";
public static final int DATABASE_VERSION = 48; // = Conversations DATABASE_VERSION + 4
public static final int DATABASE_VERSION = 51; // = Conversations DATABASE_VERSION + 5
private static DatabaseBackend instance = null;
private static String CREATE_CONTATCS_STATEMENT = "create table "
@ -152,6 +150,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
+ ");";
private static String RESOLVER_RESULTS_TABLENAME = "resolver_results";
private static String CREATE_RESOLVER_RESULTS_TABLE = "create table " + RESOLVER_RESULTS_TABLENAME + "("
+ Resolver.Result.DOMAIN + " TEXT,"
+ Resolver.Result.HOSTNAME + " TEXT,"
@ -160,8 +159,10 @@ public class DatabaseBackend extends SQLiteOpenHelper {
+ Resolver.Result.DIRECT_TLS + " NUMBER,"
+ Resolver.Result.AUTHENTICATED + " NUMBER,"
+ Resolver.Result.PORT + " NUMBER,"
+ Resolver.Result.TIME_REQUESTED + " NUMBER,"
+ "UNIQUE(" + Resolver.Result.DOMAIN + ") ON CONFLICT REPLACE"
+ ");";
private static String CREATE_MESSAGE_TIME_INDEX = "create INDEX message_time_index ON " + Message.TABLENAME + "(" + Message.TIME_SENT + ")";
private static String CREATE_MESSAGE_CONVERSATION_INDEX = "create INDEX message_conversation_index ON " + Message.TABLENAME + "(" + Message.CONVERSATION + ")";
private static String CREATE_MESSAGE_DELETED_INDEX = "create index message_deleted_index ON " + Message.TABLENAME + "(" + Message.DELETED + ")";
@ -195,7 +196,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
@Override
public void onConfigure(SQLiteDatabase db) {
db.execSQL("PRAGMA foreign_keys=ON");
db.rawQuery("PRAGMA secure_delete=ON", null);
db.rawQuery("PRAGMA secure_delete=ON", null).close();
}
@Override
@ -241,6 +242,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
+ Message.READ_BY_MARKERS + " TEXT,"
+ Message.MARKABLE + " NUMBER DEFAULT 0,"
+ Message.FILE_DELETED + " NUMBER DEFAULT 0,"
+ Message.BODY_LANGUAGE + " TEXT,"
+ Message.REMOTE_MSG_ID + " TEXT, FOREIGN KEY("
+ Message.CONVERSATION + ") REFERENCES "
+ Conversation.TABLENAME + "(" + Conversation.UUID
@ -529,10 +531,6 @@ public class DatabaseBackend extends SQLiteOpenHelper {
db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.MARKABLE + " NUMBER DEFAULT 0");
}
if (oldVersion < 40 && newVersion >= 40) {
db.execSQL(CREATE_RESOLVER_RESULTS_TABLE);
}
if (oldVersion < 42 && newVersion >= 42) {
db.execSQL(CREATE_MESSAGE_INDEX_TABLE);
db.execSQL(CREATE_MESSAGE_INSERT_TRIGGER);
@ -562,12 +560,23 @@ public class DatabaseBackend extends SQLiteOpenHelper {
}
}
if (oldVersion < 48 && newVersion >= 48) {
try {
db.execSQL(CREATE_RESOLVER_RESULTS_TABLE);
} catch (Exception e) {
//ignore
}
if (oldVersion < 49 && newVersion >= 49) {
db.execSQL("ALTER TABLE " + Message.TABLENAME + " ADD COLUMN " + Message.BODY_LANGUAGE);
}
if (oldVersion < 50 && newVersion >= 50) {
final long start = SystemClock.elapsedRealtime();
db.rawQuery("PRAGMA secure_delete = FALSE", null).close();
db.execSQL("update " + Message.TABLENAME + " set " + Message.EDITED + "=NULL");
db.rawQuery("PRAGMA secure_delete=ON", null).close();
final long diff = SystemClock.elapsedRealtime() - start;
Log.d(Config.LOGTAG, "deleted old edit information in " + diff + "ms");
}
if (oldVersion < 51 && newVersion >= 51) {
// values in resolver_result are cache and not worth to store
db.execSQL("DROP TABLE IF EXISTS " + RESOLVER_RESULTS_TABLENAME);
db.execSQL(CREATE_RESOLVER_RESULTS_TABLE);
}
}
@ -601,7 +610,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
continue;
}
String updateArgs[] = {
String[] updateArgs = {
newJid,
cursor.getString(cursor.getColumnIndex(Conversation.UUID)),
};
@ -617,14 +626,14 @@ public class DatabaseBackend extends SQLiteOpenHelper {
String newJid;
try {
newJid = Jid.of(cursor.getString(cursor.getColumnIndex(Contact.JID))).toString();
} catch (IllegalArgumentException ignored) {
} catch (final IllegalArgumentException e) {
Log.e(Config.LOGTAG, "Failed to migrate Contact JID "
+ cursor.getString(cursor.getColumnIndex(Contact.JID))
+ ": " + ignored + ". Skipping...");
+ ": Skipping...", e);
continue;
}
String updateArgs[] = {
final String[] updateArgs = {
newJid,
cursor.getString(cursor.getColumnIndex(Contact.ACCOUNT)),
cursor.getString(cursor.getColumnIndex(Contact.JID)),
@ -653,7 +662,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
continue;
}
String updateArgs[] = {
String[] updateArgs = {
newServer,
cursor.getString(cursor.getColumnIndex(Account.UUID)),
};
@ -791,12 +800,10 @@ public class DatabaseBackend extends SQLiteOpenHelper {
null, null, Message.TIME_SENT + " DESC",
String.valueOf(limit));
}
CursorUtils.upgradeCursorWindowSize(cursor);
while (cursor.moveToNext()) {
try {
final Message message = Message.fromCursor(cursor, conversation);
if (message != null && !message.isMessageDeleted()) {
list.add(0, message);
}
list.add(0, Message.fromCursor(cursor, conversation));
} catch (Exception e) {
Log.e(Config.LOGTAG, "unable to restore message");
}
@ -1009,12 +1016,14 @@ public class DatabaseBackend extends SQLiteOpenHelper {
private List<Account> getAccounts(SQLiteDatabase db) {
List<Account> list = new ArrayList<>();
Cursor cursor = db.query(Account.TABLENAME, null, null, null, null,
null, null);
while (cursor.moveToNext()) {
list.add(Account.fromCursor(cursor));
try (Cursor cursor = db.query(Account.TABLENAME, null, null, null, null,
null, null)) {
while (cursor.moveToNext()) {
list.add(Account.fromCursor(cursor));
}
} catch (Exception e) {
e.printStackTrace();
}
cursor.close();
return list;
}
@ -1052,7 +1061,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
public void readRoster(Roster roster) {
SQLiteDatabase db = this.getReadableDatabase();
Cursor cursor;
String args[] = {roster.getAccount().getUuid()};
String[] args = {roster.getAccount().getUuid()};
cursor = db.query(Contact.TABLENAME, null, Contact.ACCOUNT + "=?", args, null, null, null);
while (cursor.moveToNext()) {
roster.initContact(Contact.fromCursor(cursor));

View file

@ -13,6 +13,8 @@ import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
import android.graphics.pdf.PdfRenderer;
import android.media.MediaMetadataRetriever;
import android.media.MediaScannerConnection;
import android.net.Uri;
@ -21,15 +23,17 @@ import android.os.Environment;
import android.os.ParcelFileDescriptor;
import android.provider.MediaStore;
import android.provider.OpenableColumns;
import android.support.annotation.RequiresApi;
import android.support.v4.content.FileProvider;
import android.system.Os;
import android.system.StructStat;
import android.util.Base64;
import android.util.Base64OutputStream;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.LruCache;
import androidx.annotation.RequiresApi;
import androidx.core.content.FileProvider;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
@ -41,6 +45,7 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URL;
import java.security.DigestOutputStream;
@ -57,8 +62,10 @@ import de.pixart.messenger.Config;
import de.pixart.messenger.R;
import de.pixart.messenger.entities.DownloadableFile;
import de.pixart.messenger.entities.Message;
import de.pixart.messenger.services.AttachFileToConversationRunnable;
import de.pixart.messenger.services.XmppConnectionService;
import de.pixart.messenger.ui.util.Attachment;
import de.pixart.messenger.utils.Compatibility;
import de.pixart.messenger.utils.CryptoHelper;
import de.pixart.messenger.utils.ExifHelper;
import de.pixart.messenger.utils.FileUtils;
@ -75,6 +82,7 @@ public class FileBackend {
private static final SimpleDateFormat fileDateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US);
private static final String FILE_PROVIDER = ".files";
private static final String APP_DIRECTORY = "Pix-Art Messenger";
private XmppConnectionService mXmppConnectionService;
@ -268,6 +276,7 @@ public class FileBackend {
}
public static boolean allFilesUnderSize(Context context, List<Attachment> attachments, long max) {
final boolean compressVideo = !AttachFileToConversationRunnable.getVideoCompression(context).equals("uncompressed");
if (max <= 0) {
Log.d(Config.LOGTAG, "server did not report max file size for http upload");
return true; //exception to be compatible with HTTP Upload < v0.2
@ -277,7 +286,7 @@ public class FileBackend {
continue;
}
String mime = attachment.getMime();
if (mime != null && mime.startsWith("video/")) {
if (mime != null && mime.startsWith("video/") && compressVideo) {
try {
Dimensions dimensions = FileBackend.getVideoDimensions(context, attachment.getUri());
if (dimensions.getMin() >= 720) {
@ -296,12 +305,12 @@ public class FileBackend {
return true;
}
public List<Attachment> convertToAttachments(List<DatabaseBackend.FilePath> relativeFilePaths) {
List<Attachment> attachments = new ArrayList<>();
public List<Attachment> convertToAttachments(final List<DatabaseBackend.FilePath> relativeFilePaths) {
final List<Attachment> attachments = new ArrayList<>();
for (DatabaseBackend.FilePath relativeFilePath : relativeFilePaths) {
final String mime = MimeUtils.guessMimeTypeFromExtension(MimeUtils.extractRelevantExtension(relativeFilePath.path));
final File file = getFileForPath(relativeFilePath.path, mime);
if (file.exists() && mime != null && (mime.startsWith("image/") || mime.startsWith("video/"))) {
if (file.exists()) {
attachments.add(Attachment.of(relativeFilePath.uuid, file, mime));
}
}
@ -310,26 +319,26 @@ public class FileBackend {
public static String getConversationsDirectory(final String type) {
if (type.equalsIgnoreCase("null")) {
return Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + "Pix-Art Messenger" + "/";
return Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + APP_DIRECTORY + File.separator;
} else {
return getAppMediaDirectory() + "Pix-Art Messenger" + " " + type + "/";
return getAppMediaDirectory() + APP_DIRECTORY + " " + type + File.separator;
}
}
public static String getAppMediaDirectory() {
return Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + "Pix-Art Messenger" + "/Media/";
return Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + APP_DIRECTORY + File.separator + "Media" + File.separator;
}
public static String getBackupDirectory() {
return Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + "Pix-Art Messenger" + "/Database/";
return Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + APP_DIRECTORY + File.separator + "Database" + File.separator;
}
public static String getAppLogsDirectory() {
return Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + "Pix-Art Messenger" + "/Chats/";
return Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + APP_DIRECTORY + File.separator + "Chats" + File.separator;
}
public static String getAppUpdateDirectory() {
return Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + "Pix-Art Messenger" + "/Update/";
return Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + APP_DIRECTORY + File.separator + "Update" + File.separator;
}
private Bitmap resize(final Bitmap originalBitmap, int size) throws IOException {
@ -643,9 +652,11 @@ public class FileBackend {
}
DownloadableFile file = getFile(message);
final String mime = file.getMimeType();
if (mime.startsWith("video/")) {
if ("application/pdf".equals(mime) && Compatibility.runsTwentyOne()) {
thumbnail = getPDFPreview(file, size);
} else if (mime.startsWith("video/")) {
thumbnail = getVideoPreview(file, size);
} else {
} else if (mime.startsWith("image/")) {
Bitmap fullsize = getFullsizeImagePreview(file, size);
if (fullsize == null) {
throw new FileNotFoundException();
@ -665,6 +676,46 @@ public class FileBackend {
return thumbnail;
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private Bitmap getPDFPreview(final File file, int size) {
try {
final ParcelFileDescriptor mFileDescriptor = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
if (mFileDescriptor == null) {
return null;
}
final PdfRenderer renderer = new PdfRenderer(mFileDescriptor);
final PdfRenderer.Page page = renderer.openPage(0);
final Dimensions dimensions = scalePdfDimensions(new Dimensions(page.getHeight(), page.getWidth()));
final Bitmap bitmap = Bitmap.createBitmap(dimensions.width, dimensions.height, Bitmap.Config.ARGB_8888);
bitmap.eraseColor(Color.WHITE);
page.render(bitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY);
drawOverlay(bitmap, R.drawable.show_pdf, 0.75f);
page.close();
renderer.close();
return bitmap;
} catch (Exception e) {
e.printStackTrace();
final Bitmap placeholder = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
placeholder.eraseColor(Color.WHITE);
drawOverlay(placeholder, R.drawable.show_pdf, 0.75f);
return placeholder;
}
}
private Dimensions scalePdfDimensions(final Dimensions dimensions) {
final DisplayMetrics displayMetrics = mXmppConnectionService.getResources().getDisplayMetrics();
final int target = (int) (displayMetrics.density * 288);
final int w, h;
if (dimensions.width <= dimensions.height) {
w = Math.max((int) (dimensions.width / ((double) dimensions.height / target)), 1);
h = target;
} else {
w = target;
h = Math.max((int) (dimensions.height / ((double) dimensions.width / target)), 1);
}
return new Dimensions(h, w);
}
private Bitmap getFullsizeImagePreview(File file, int size) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = calcSampleSize(file, size);
@ -676,17 +727,53 @@ public class FileBackend {
}
}
private void drawOverlay(Bitmap bitmap, int resource, float factor) {
public void drawOverlay(final Bitmap bitmap, final int resource, final float factor) {
drawOverlay(bitmap, resource, factor, false);
}
public void drawOverlay(final Bitmap bitmap, final int resource, final float factor, final boolean corner) {
Bitmap overlay = BitmapFactory.decodeResource(mXmppConnectionService.getResources(), resource);
Canvas canvas = new Canvas(bitmap);
float targetSize = Math.min(canvas.getWidth(), canvas.getHeight()) * factor;
Log.d(Config.LOGTAG, "target size overlay: " + targetSize + " overlay bitmap size was " + overlay.getHeight());
float left;
float top;
if (corner) {
left = canvas.getWidth() - targetSize;
top = canvas.getHeight() - targetSize;
} else {
left = (canvas.getWidth() - targetSize) / 2.0f;
top = (canvas.getHeight() - targetSize) / 2.0f;
}
RectF dst = new RectF(left, top, left + targetSize - 1, top + targetSize - 1);
canvas.drawBitmap(overlay, null, dst, createAntiAliasingPaint());
}
public void drawOverlayFromDrawable(final Drawable drawable, final int resource, final float factor) {
Bitmap overlay = BitmapFactory.decodeResource(mXmppConnectionService.getResources(), resource);
Bitmap original = drawableToBitmap(drawable);
Canvas canvas = new Canvas(original);
float targetSize = Math.min(canvas.getWidth(), canvas.getHeight()) * factor;
Log.d(Config.LOGTAG, "target size overlay: " + targetSize + " overlay bitmap size was " + overlay.getHeight());
float left = (canvas.getWidth() - targetSize) / 2.0f;
float top = (canvas.getHeight() - targetSize) / 2.0f;
RectF dst = new RectF(left, top, left + targetSize - 1, top + targetSize - 1);
canvas.drawBitmap(overlay, null, dst, createAntiAliasingPaint());
}
private static Bitmap drawableToBitmap(Drawable drawable) {
Bitmap bitmap = null;
if (drawable.getIntrinsicWidth() <= 0 || drawable.getIntrinsicHeight() <= 0) {
bitmap = Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888); // Single color bitmap will be created of 1x1 pixel
} else {
bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
}
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
drawable.draw(canvas);
return bitmap;
}
private static Paint createAntiAliasingPaint() {
Paint paint = new Paint();
paint.setAntiAlias(true);
@ -710,15 +797,18 @@ public class FileBackend {
}
}
private Bitmap getVideoPreview(File file, int size) throws IOException {
MediaMetadataRetriever metadataRetriever = new MediaMetadataRetriever();
private Bitmap getVideoPreview(final File file, final int size) {
final MediaMetadataRetriever metadataRetriever = new MediaMetadataRetriever();
Bitmap frame;
try {
metadataRetriever.setDataSource(file.getAbsolutePath());
frame = metadataRetriever.getFrameAtTime(0);
metadataRetriever.release();
frame = resize(frame, size);
} catch (IOException | RuntimeException e) {
} catch (IOException e) {
frame = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
frame.eraseColor(0xff000000);
} catch (RuntimeException e) {
frame = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
frame.eraseColor(0xff000000);
}
@ -731,7 +821,7 @@ public class FileBackend {
}
public Uri getTakePhotoUri() {
File file = new File(getTakeFromCameraPath() + "IMG_" + this.fileDateFormat.format(new Date()) + ".jpg");
File file = new File(getTakeFromCameraPath() + "IMG_" + fileDateFormat.format(new Date()) + ".jpg");
file.getParentFile().mkdirs();
return getUriForFile(mXmppConnectionService, file);
}
@ -757,7 +847,7 @@ public class FileBackend {
}
public Uri getTakeVideoUri() {
File file = new File(getTakeFromCameraPath() + "VID_" + this.fileDateFormat.format(new Date()) + ".mp4");
File file = new File(getTakeFromCameraPath() + "VID_" + fileDateFormat.format(new Date()) + ".mp4");
file.getParentFile().mkdirs();
return getUriForFile(mXmppConnectionService, file);
}
@ -879,7 +969,9 @@ public class FileBackend {
avatar.width = options.outWidth;
avatar.type = options.outMimeType;
return avatar;
} catch (NoSuchAlgorithmException | IOException e) {
} catch (NoSuchAlgorithmException e) {
return null;
} catch (IOException e) {
return null;
} finally {
close(is);
@ -897,7 +989,7 @@ public class FileBackend {
file = new File(getAvatarPath(avatar.getFilename()));
avatar.size = file.length();
} else {
file = new File(mXmppConnectionService.getCacheDir().getAbsolutePath() + "/" + UUID.randomUUID().toString());
file = new File(mXmppConnectionService.getCacheDir().getAbsolutePath() + File.separator + UUID.randomUUID().toString());
if (file.getParentFile().mkdirs()) {
Log.d(Config.LOGTAG, "created cache directory");
}
@ -933,7 +1025,11 @@ public class FileBackend {
return false;
}
avatar.size = bytes.length;
} catch (IllegalArgumentException | IOException | NoSuchAlgorithmException e) {
} catch (IllegalArgumentException e) {
return false;
} catch (IOException e) {
return false;
} catch (NoSuchAlgorithmException e) {
return false;
} finally {
close(os);
@ -969,7 +1065,10 @@ public class FileBackend {
input = rotate(input, getRotation(image));
return cropCenterSquare(input, size);
}
} catch (FileNotFoundException | SecurityException e) {
} catch (FileNotFoundException e) {
Log.d(Config.LOGTAG, "unable to open file " + image.toString(), e);
return null;
} catch (SecurityException e) {
Log.d(Config.LOGTAG, "unable to open file " + image.toString(), e);
return null;
} finally {
@ -1085,14 +1184,22 @@ public class FileBackend {
final boolean audio = mime != null && mime.startsWith("audio/");
final boolean vcard = mime != null && mime.contains("vcard");
final boolean apk = mime != null && mime.equals("application/vnd.android.package-archive");
final boolean pdf = "application/pdf".equals(mime);
final StringBuilder body = new StringBuilder();
if (url != null) {
body.append(url.toString());
}
body.append('|').append(file.getSize());
if (image || video) {
if (image || video || (pdf && Compatibility.runsTwentyOne())) {
try {
Dimensions dimensions = image ? getImageDimensions(file) : getVideoDimensions(file);
final Dimensions dimensions;
if (video) {
dimensions = getVideoDimensions(file);
} else if (pdf && Compatibility.runsTwentyOne()) {
dimensions = getPDFDimensions(file);
} else {
dimensions = getImageDimensions(file);
}
if (dimensions.valid()) {
body.append('|').append(dimensions.width).append('|').append(dimensions.height);
}
@ -1101,7 +1208,7 @@ public class FileBackend {
//fall threw
}
} else if (audio) {
body.append("|0|0|").append(getMediaRuntime(file));
body.append("|0|0|").append(getMediaRuntime(file)).append('|').append(getAudioTitleArtist(file));
} else if (vcard) {
body.append("|0|0|0|").append(getVCard(file));
} else if (apk) {
@ -1112,6 +1219,37 @@ public class FileBackend {
message.setType(privateMessage ? Message.TYPE_PRIVATE_FILE : (image ? Message.TYPE_IMAGE : Message.TYPE_FILE));
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private Dimensions getPDFDimensions(final File file) {
final ParcelFileDescriptor fileDescriptor;
try {
fileDescriptor = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);
if (fileDescriptor == null) {
return new Dimensions(0, 0);
}
} catch (FileNotFoundException e) {
return new Dimensions(0, 0);
}
try {
final PdfRenderer pdfRenderer = new PdfRenderer(fileDescriptor);
final PdfRenderer.Page page = pdfRenderer.openPage(0);
final int height = page.getHeight();
final int width = page.getWidth();
page.close();
pdfRenderer.close();
return scalePdfDimensions(new Dimensions(height, width));
} catch (IOException e) {
Log.d(Config.LOGTAG, "unable to get dimensions for pdf document", e);
return new Dimensions(0, 0);
}
}
public static void updateFileParams(Message message, URL url, long size) {
final StringBuilder body = new StringBuilder();
body.append(url.toString()).append('|').append(size);
message.setBody(body.toString());
}
private int getMediaRuntime(File file) {
try {
MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
@ -1122,6 +1260,47 @@ public class FileBackend {
}
}
private String getAudioTitleArtist(final File file) {
String artist;
String title;
StringBuilder builder = new StringBuilder();
try {
MediaMetadataRetriever mediaMetadataRetriever = new MediaMetadataRetriever();
mediaMetadataRetriever.setDataSource(file.toString());
artist = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ARTIST);
if (artist == null) {
artist = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST);
}
if (artist == null) {
artist = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_COMPOSER);
}
title = mediaMetadataRetriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_TITLE);
mediaMetadataRetriever.release();
boolean separator = false;
if (artist != null && artist.length() > 0) {
builder.append(artist);
separator = true;
}
if (title != null && title.length() > 0) {
if (separator) {
builder.append(" - ");
}
builder.append(title);
}
try {
final String s = builder.substring(0, Math.min(128, builder.length()));
final byte[] data = s.trim().getBytes("UTF-8");
return Base64.encodeToString(data, Base64.DEFAULT);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return "";
}
} catch (Exception e) {
e.printStackTrace();
return "";
}
}
private String getAPK(File file, Context context) {
String APKName;
final PackageManager pm = context.getPackageManager();
@ -1204,7 +1383,10 @@ public class FileBackend {
if (bitmap != null || cacheOnly) {
return bitmap;
}
if (attachment.getMime() != null && attachment.getMime().startsWith("video/")) {
DownloadableFile file = new DownloadableFile(attachment.getUri().getPath());
if ("application/pdf".equals(attachment.getMime()) && Compatibility.runsTwentyOne()) {
bitmap = cropCenterSquare(getPDFPreview(file, size), size);
} else if (attachment.getMime() != null && attachment.getMime().startsWith("video/")) {
bitmap = cropCenterSquareVideo(attachment.getUri(), size);
drawOverlay(bitmap, R.drawable.play_video, 0.75f);
} else {
@ -1351,24 +1533,35 @@ public class FileBackend {
return getFile(message).exists();
}
public static void close(Closeable stream) {
public static void close(final Closeable stream) {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
} catch (Exception e) {
Log.d(Config.LOGTAG, "unable to close stream", e);
}
}
}
public static void close(Socket socket) {
public static void close(final Socket socket) {
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
Log.d(Config.LOGTAG, "unable to close socket", e);
}
}
}
public static void close(final ServerSocket socket) {
if (socket != null) {
try {
socket.close();
} catch (IOException e) {
Log.d(Config.LOGTAG, "unable to close socket", e);
}
}
}
public static boolean weOwnFile(Context context, Uri uri) {
if (uri == null || !ContentResolver.SCHEME_FILE.equals(uri.getScheme())) {

View file

@ -1,5 +1,5 @@
package de.pixart.messenger.persistance;
public interface OnPhoneContactsMerged {
public void phoneContactsMerged();
void phoneContactsMerged();
}

View file

@ -5,6 +5,14 @@ import android.os.PowerManager;
import android.os.SystemClock;
import android.util.Log;
import org.bouncycastle.crypto.engines.AESEngine;
import org.bouncycastle.crypto.io.CipherInputStream;
import org.bouncycastle.crypto.io.CipherOutputStream;
import org.bouncycastle.crypto.modes.AEADBlockCipher;
import org.bouncycastle.crypto.modes.GCMBlockCipher;
import org.bouncycastle.crypto.params.AEADParameters;
import org.bouncycastle.crypto.params.KeyParameter;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
@ -15,12 +23,7 @@ import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.util.concurrent.atomic.AtomicLong;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
import javax.crypto.CipherOutputStream;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import de.pixart.messenger.Config;
import de.pixart.messenger.R;
@ -28,10 +31,7 @@ import de.pixart.messenger.entities.DownloadableFile;
import de.pixart.messenger.utils.Compatibility;
public class AbstractConnectionManager {
private static final String KEYTYPE = "AES";
private static final String CIPHERMODE = "AES/GCM/NoPadding";
private static final String PROVIDER = "BC";
private static final int UI_REFRESH_THRESHOLD = 250;
private static final int UI_REFRESH_THRESHOLD = Config.REFRESH_UI_INTERVAL;
private static final AtomicLong LAST_UI_UPDATE_CALL = new AtomicLong(0);
protected XmppConnectionService mXmppConnectionService;
@ -41,34 +41,19 @@ public class AbstractConnectionManager {
public static InputStream upgrade(DownloadableFile file, InputStream is) throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, InvalidKeyException, NoSuchPaddingException, NoSuchProviderException {
if (file.getKey() != null && file.getIv() != null) {
final Cipher cipher = Compatibility.twentyEight() ? Cipher.getInstance(CIPHERMODE) : Cipher.getInstance(CIPHERMODE, PROVIDER);
SecretKeySpec keySpec = new SecretKeySpec(file.getKey(), KEYTYPE);
IvParameterSpec ivSpec = new IvParameterSpec(file.getIv());
cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec);
AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine());
cipher.init(true, new AEADParameters(new KeyParameter(file.getKey()), 128, file.getIv()));
return new CipherInputStream(is, cipher);
} else {
return is;
}
}
public static OutputStream createAppendedOutputStream(DownloadableFile file) {
return createOutputStream(file, false, true);
}
public static OutputStream createOutputStream(DownloadableFile file) {
return createOutputStream(file, false);
}
public static OutputStream createOutputStream(DownloadableFile file, boolean gcm) {
return createOutputStream(file, gcm, false);
}
private static OutputStream createOutputStream(DownloadableFile file, boolean gcm, boolean append) {
public static OutputStream createOutputStream(DownloadableFile file, boolean append, boolean decrypt) {
FileOutputStream os;
try {
os = new FileOutputStream(file, append);
if (file.getKey() == null) {
if (file.getKey() == null || !decrypt) {
return os;
}
} catch (FileNotFoundException e) {
@ -76,18 +61,9 @@ public class AbstractConnectionManager {
return null;
}
try {
if (gcm) {
final Cipher cipher = Compatibility.twentyEight() ? Cipher.getInstance(CIPHERMODE) : Cipher.getInstance(CIPHERMODE, PROVIDER);
SecretKeySpec keySpec = new SecretKeySpec(file.getKey(), KEYTYPE);
IvParameterSpec ivSpec = new IvParameterSpec(file.getIv());
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
return new CipherOutputStream(os, cipher);
} else {
IvParameterSpec ips = new IvParameterSpec(file.getIv());
final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(file.getKey(), KEYTYPE), ips);
return new CipherOutputStream(os, cipher);
}
AEADBlockCipher cipher = new GCMBlockCipher(new AESEngine());
cipher.init(false, new AEADParameters(new KeyParameter(file.getKey()), 128, file.getIv()));
return new CipherOutputStream(os, cipher);
} catch (Exception e) {
Log.d(Config.LOGTAG, "unable to create cipher output stream", e);
return null;
@ -138,4 +114,23 @@ public class AbstractConnectionManager {
PowerManager powerManager = (PowerManager) mXmppConnectionService.getSystemService(Context.POWER_SERVICE);
return powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, name);
}
public static class Extension {
public final String main;
public final String secondary;
private Extension(String main, String secondary) {
this.main = main;
this.secondary = secondary;
}
public static Extension of(String path) {
final int pos = path.lastIndexOf('/');
final String filename = path.substring(pos + 1).toLowerCase();
final String[] parts = filename.split("\\.");
final String main = parts.length >= 2 ? parts[parts.length - 1] : null;
final String secondary = parts.length >= 3 ? parts[parts.length - 2] : null;
return new Extension(main, secondary);
}
}
}

View file

@ -1,31 +0,0 @@
package de.pixart.messenger.services;
import android.content.Context;
import android.os.Build;
import android.support.text.emoji.EmojiCompat;
import android.util.Log;
import de.pixart.messenger.Config;
public abstract class AbstractEmojiService {
protected final Context context;
public AbstractEmojiService(Context context) {
this.context = context;
}
protected abstract EmojiCompat.Config buildConfig();
public void init(boolean useBundledEmoji) {
Log.d(Config.LOGTAG, "Emojis: use integrated lib " + useBundledEmoji);
final EmojiCompat.Config config = buildConfig();
//On recent Androids we assume to have the latest emojis
//there are some annoying bugs with emoji compat that make it a safer choice not to use it when possible
// a) when using the ondemand emoji font (play store) flags dont work
// b) the text preview has annoying glitches when the cut of text contains emojis (the emoji will be half visible)
config.setReplaceAll(useBundledEmoji && Build.VERSION.SDK_INT < Build.VERSION_CODES.O);
EmojiCompat.init(config);
}
}

View file

@ -11,7 +11,7 @@ public abstract class AbstractQuickConversationsService {
public abstract void considerSync();
public static boolean isQuicksy() {
return false;
return true;
}
public static boolean isConversations() {

View file

@ -1,11 +1,15 @@
package de.pixart.messenger.services;
import android.content.Context;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.support.annotation.RequiresApi;
import android.preference.PreferenceManager;
import android.util.Log;
import androidx.annotation.RequiresApi;
import net.ypresto.androidtranscoder.MediaTranscoder;
import net.ypresto.androidtranscoder.format.MediaFormatStrategyPresets;
@ -178,6 +182,11 @@ public class AttachFileToConversationRunnable implements Runnable, MediaTranscod
}
}
public static String getVideoCompression(final Context context) {
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
return preferences.getString("video_compression", context.getResources().getString(R.string.video_compression));
}
public FileBackend getFileBackend() {
return mXmppConnectionService.fileBackend;
}

View file

@ -12,8 +12,7 @@ import android.media.AudioManager;
import android.os.Build;
import android.os.Handler;
import android.os.PowerManager;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.util.Base64;
import android.util.Log;
import android.view.View;
import android.widget.ImageButton;
@ -21,6 +20,10 @@ import android.widget.RelativeLayout;
import android.widget.SeekBar;
import android.widget.TextView;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import java.io.UnsupportedEncodingException;
import java.lang.ref.WeakReference;
import java.util.Locale;
import java.util.concurrent.ExecutorService;
@ -89,7 +92,7 @@ public class AudioPlayer implements View.OnClickListener, MediaPlayer.OnCompleti
audioPlayer.setTag(message);
if (init(ViewHolder.get(audioPlayer), message)) {
this.audioPlayerLayouts.addWeakReferenceTo(audioPlayer);
executor.execute(()-> this.stopRefresher(true));
executor.execute(() -> this.stopRefresher(true));
} else {
this.audioPlayerLayouts.removeWeakReferenceTo(audioPlayer);
}
@ -105,7 +108,7 @@ public class AudioPlayer implements View.OnClickListener, MediaPlayer.OnCompleti
}
viewHolder.progress.setOnSeekBarChangeListener(this);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
ColorStateList color = ContextCompat.getColorStateList(messageAdapter.getContext(), viewHolder.darkBackground ? R.color.white70 : R.color.bubble);
ColorStateList color = viewHolder.darkBackground ? ContextCompat.getColorStateList(messageAdapter.getContext(), R.color.white70) : viewHolder.isOrange ? ContextCompat.getColorStateList(messageAdapter.getContext(), R.color.darkorange) : ContextCompat.getColorStateList(messageAdapter.getContext(), R.color.darkblue);
viewHolder.progress.setThumbTintList(color);
viewHolder.progress.setProgressTintList(color);
}
@ -139,9 +142,9 @@ public class AudioPlayer implements View.OnClickListener, MediaPlayer.OnCompleti
}
private void startStop(ImageButton playPause) {
if (ContextCompat.checkSelfPermission(messageAdapter.getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
if (ContextCompat.checkSelfPermission(messageAdapter.getActivity(), Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(messageAdapter.getActivity(), Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
pendingOnClickView.push(new WeakReference<>(playPause));
ActivityCompat.requestPermissions(messageAdapter.getActivity(), new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, ConversationsActivity.REQUEST_PLAY_PAUSE);
ActivityCompat.requestPermissions(messageAdapter.getActivity(), new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.READ_EXTERNAL_STORAGE}, ConversationsActivity.REQUEST_PLAY_PAUSE);
return;
}
initializeProximityWakeLock(playPause.getContext());
@ -343,8 +346,12 @@ public class AudioPlayer implements View.OnClickListener, MediaPlayer.OnCompleti
return false;
}
final ViewHolder viewHolder = ViewHolder.get(audioPlayer);
viewHolder.progress.setProgress(current * 100 / duration);
viewHolder.runtime.setText(formatTime(current) + " / " + formatTime(duration));
if (duration <= 0) {
viewHolder.progress.setProgress(100);
} else {
viewHolder.progress.setProgress(current * 100 / duration);
}
viewHolder.runtime.setText(String.format("%s / %s", formatTime(current), formatTime(duration)));
return true;
}
@ -373,6 +380,7 @@ public class AudioPlayer implements View.OnClickListener, MediaPlayer.OnCompleti
try {
ViewHolder currentViewHolder = getCurrentViewHolder();
if (currentViewHolder != null) {
messageAdapter.getActivity().setVolumeControlStream(streamType);
play(currentViewHolder, currentlyPlayingMessage, streamType == AudioManager.STREAM_VOICE_CALL, progress);
}
} catch (Exception e) {
@ -417,6 +425,7 @@ public class AudioPlayer implements View.OnClickListener, MediaPlayer.OnCompleti
private SeekBar progress;
private ImageButton playPause;
private boolean darkBackground = false;
private boolean isOrange = false;
public static ViewHolder get(RelativeLayout audioPlayer) {
ViewHolder viewHolder = (ViewHolder) audioPlayer.getTag(R.id.TAG_AUDIO_PLAYER_VIEW_HOLDER);
@ -430,8 +439,9 @@ public class AudioPlayer implements View.OnClickListener, MediaPlayer.OnCompleti
return viewHolder;
}
public void setDarkBackground(boolean darkBackground) {
public void setTheme(boolean darkBackground, boolean isOrange) {
this.darkBackground = darkBackground;
this.isOrange = isOrange;
}
}
}

View file

@ -13,14 +13,15 @@ import android.graphics.Typeface;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.support.annotation.ColorInt;
import android.support.annotation.Nullable;
import android.support.v4.content.res.ResourcesCompat;
import android.text.TextUtils;
import android.util.DisplayMetrics;
import android.util.Log;
import android.util.LruCache;
import androidx.annotation.ColorInt;
import androidx.annotation.Nullable;
import androidx.core.content.res.ResourcesCompat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
@ -38,7 +39,8 @@ import de.pixart.messenger.entities.Conversational;
import de.pixart.messenger.entities.ListItem;
import de.pixart.messenger.entities.Message;
import de.pixart.messenger.entities.MucOptions;
import de.pixart.messenger.http.services.MuclumbusService;
import de.pixart.messenger.entities.RawBlockable;
import de.pixart.messenger.entities.Room;
import de.pixart.messenger.utils.UIHelper;
import de.pixart.messenger.xmpp.OnAdvancedStreamFeaturesLoaded;
import de.pixart.messenger.xmpp.XmppConnection;
@ -58,6 +60,7 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded {
private static final String PREFIX_CONVERSATION = "conversation";
private static final String PREFIX_ACCOUNT = "account";
private static final String PREFIX_GENERIC = "generic";
private static final String CHANNEL_SYMBOL = "#";
final private ArrayList<Integer> sizes = new ArrayList<>();
final private HashMap<String, Set<String>> conversationDependentKeys = new HashMap<>();
@ -83,19 +86,19 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded {
return get((ListItem) avatarable, size, cachedOnly);
} else if (avatarable instanceof MucOptions.User) {
return get((MucOptions.User) avatarable, size, cachedOnly);
} else if (avatarable instanceof MuclumbusService.Room) {
return get((MuclumbusService.Room) avatarable, size, cachedOnly);
} else if (avatarable instanceof Room) {
return get((Room) avatarable, size, cachedOnly);
}
throw new AssertionError("AvatarService does not know how to generate avatar from "+avatarable.getClass().getName());
throw new AssertionError("AvatarService does not know how to generate avatar from " + avatarable.getClass().getName());
}
private Bitmap get(final MuclumbusService.Room result, final int size, boolean cacheOnly) {
private Bitmap get(final Room result, final int size, boolean cacheOnly) {
final Jid room = result.getRoom();
Conversation conversation = room != null ? mXmppConnectionService.findFirstMuc(room) : null;
if (conversation != null) {
return get(conversation, size, cacheOnly);
}
return get(result.getName(), room != null ? room.asBareJid().toEscapedString() : result.getName(), size, cacheOnly);
return get(CHANNEL_SYMBOL, room != null ? room.asBareJid().toEscapedString() : result.getName(), size, cacheOnly);
}
private Bitmap get(final Contact contact, final int size, boolean cachedOnly) {
@ -275,7 +278,9 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded {
}
public Bitmap get(ListItem item, int size, boolean cachedOnly) {
if (item instanceof Contact) {
if (item instanceof RawBlockable) {
return get(item.getDisplayName(), item.getJid().toEscapedString(), size, cachedOnly);
} else if (item instanceof Contact) {
return get((Contact) item, size, cachedOnly);
} else if (item instanceof Bookmark) {
Bookmark bookmark = (Bookmark) item;
@ -338,12 +343,16 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded {
bitmap = mXmppConnectionService.getFileBackend().getAvatar(mucOptions.getAvatar(), size);
if (bitmap == null) {
final List<MucOptions.User> users = mucOptions.getUsersRelevantForNameAndAvatar();
if (users.size() == 0) {
Conversation c = mucOptions.getConversation();
bitmap = getImpl(c.getName().toString(), c.getJid().asBareJid().toString(), size);
Conversation c = mucOptions.getConversation();
if (mucOptions.isPrivateAndNonAnonymous()) {
final List<MucOptions.User> users = mucOptions.getUsersRelevantForNameAndAvatar();
if (users.size() == 0) {
bitmap = getImpl(c.getName().toString(), c.getJid().asBareJid().toString(), size);
} else {
bitmap = getImpl(users, size);
}
} else {
bitmap = getImpl(users, size);
bitmap = getImpl(CHANNEL_SYMBOL, c.getJid().asBareJid().toString(), size);
}
}
@ -627,7 +636,7 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded {
private static boolean drawTile(Canvas canvas, String name, String seed, int left, int top, int right, int bottom) {
if (name != null) {
final String letter = getFirstLetter(name);
final String letter = name.equals(CHANNEL_SYMBOL) ? name : getFirstLetter(name);
final int color = UIHelper.getColorForName(seed == null ? name : seed);
drawTile(canvas, letter, color, left, top, right, bottom);
return true;

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