forked from mirror/monocles_chat
Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
e1d59a79f2
40 changed files with 621 additions and 180 deletions
16
README-en.md
16
README-en.md
|
@ -21,13 +21,27 @@ The changes aim to improve usability and ease transition from pre-installed and
|
|||
|
||||
<img src="https://codeberg.org/Arne/monocles_chat/raw/branch/master/fastlane/metadata/android/en-US/phoneScreenshots/00.png" width="200" /> <img src="https://codeberg.org/Arne/monocles_chat/raw/branch/master/fastlane/metadata/android/en-US/phoneScreenshots/01.png" width="200" /> <img src="https://codeberg.org/Arne/monocles_chat/raw/branch/master/fastlane/metadata/android/en-US/phoneScreenshots/02.png" width="200" /> <img src="https://codeberg.org/Arne/monocles_chat/raw/branch/master/fastlane/metadata/android/en-US/phoneScreenshots/03.png" width="200" /> <img src="https://codeberg.org/Arne/monocles_chat/raw/branch/master/fastlane/metadata/android/en-US/phoneScreenshots/04.png" width="200" /> <img src="https://codeberg.org/Arne/monocles_chat/raw/branch/master/fastlane/metadata/android/en-US/phoneScreenshots/05.png" width="200" /> <img src="https://codeberg.org/Arne/monocles_chat/raw/branch/master/fastlane/metadata/android/en-US/phoneScreenshots/06.png" width="200" /> <img src="https://codeberg.org/Arne/monocles_chat/raw/branch/master/fastlane/metadata/android/en-US/phoneScreenshots/07.png" width="200" />
|
||||
|
||||
### Presettings
|
||||
|
||||
monocles chat has different presettings compared to blabber.im:
|
||||
|
||||
* don't show previews of weblinks in chat
|
||||
* don't show previews of locations in chat
|
||||
* use inner storage (files are hidden then and not shown in the Gallery)
|
||||
* don't automatically download all atachments
|
||||
|
||||
### OTR
|
||||
|
||||
monocles chat supports OTR encryption! Though it's not easy to use OTR does have some advantages:
|
||||
<a href="https://en.wikipedia.org/wiki/Off-the-Record_Messaging#Implementation">https://en.wikipedia.org/wiki/Off-the-Record_Messaging#Implementation</a>
|
||||
|
||||
## Download
|
||||
monocles chat is available for install in the F-Droid
|
||||
Alternatively release and beta-release APKs are available via codeberg: [Releases](https://codeberg.org/Arne/monocles_chat/releases/latest)
|
||||
|
||||
#### monocles chat nightly and beta
|
||||
|
||||
nightly or beta-release APKs are available via codeberg: [Releases](https://codeberg.org/Arne/monocles_chat/releases/nightly)
|
||||
nightly or beta-release APKs are available via codeberg: [Releases](https://codeberg.org/Arne/monocles_chat/releases)
|
||||
|
||||
## Social Media
|
||||
Follow us on <a rel="me" href="https://monocles.social/@monocles">monocles social</a>
|
||||
|
|
11
build.gradle
11
build.gradle
|
@ -34,13 +34,12 @@ configurations {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
playstoreImplementation('com.google.firebase:firebase-messaging:22.0.0') {
|
||||
playstoreImplementation('com.google.firebase:firebase-messaging:23.0.2') {
|
||||
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:2.2'
|
||||
playstoreImplementation 'com.google.gms:google-services:4.3.8'
|
||||
implementation 'org.sufficientlysecure:openpgp-api:10.0'
|
||||
implementation('com.theartofdev.edmodo:android-image-cropper:2.8.0') {
|
||||
exclude group: 'com.android.support', module: 'appcompat-v7'
|
||||
|
@ -86,10 +85,10 @@ dependencies {
|
|||
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
|
||||
implementation 'com.google.guava:guava:31.0.1-android'
|
||||
implementation 'com.github.AppIntro:AppIntro:6.1.0'
|
||||
implementation 'androidx.browser:browser:1.3.0' // 1.4.0 needs minCompileSdk 31
|
||||
implementation 'androidx.browser:browser:1.4.0'
|
||||
implementation 'com.otaliastudios:transcoder:0.9.1' // 0.10.4 seems to be buggy
|
||||
implementation project(':libs:AXML')
|
||||
implementation fileTree(include: ['libwebrtc-m92.aar'], dir: 'libs')
|
||||
implementation fileTree(include: ['libwebrtc-m99.aar'], dir: 'libs')
|
||||
}
|
||||
|
||||
ext {
|
||||
|
@ -107,8 +106,8 @@ android {
|
|||
targetSdkVersion 30
|
||||
|
||||
//versionNameSuffix " beta_(2021-12-19)" // " beta_(XXXX-XX-XX)" // activate for beta versions
|
||||
versionCode 112
|
||||
versionName "1.5.1"
|
||||
versionCode 113
|
||||
versionName "1.5.2"
|
||||
//resConfigs "en"
|
||||
|
||||
archivesBaseName += "-$versionName"
|
||||
|
|
|
@ -11,9 +11,9 @@
|
|||
"type": "SINGLE",
|
||||
"filters": [],
|
||||
"attributes": [],
|
||||
"versionCode": 112,
|
||||
"versionName": "1.5.1",
|
||||
"outputFile": "monocles chat-1.5.1null-git-release.apk"
|
||||
"versionCode": 113,
|
||||
"versionName": "1.5.2",
|
||||
"outputFile": "monocles chat-1.5.2null-git-release.apk"
|
||||
}
|
||||
],
|
||||
"elementType": "File"
|
||||
|
|
BIN
playstore/release/monocles chat-1.5.2null-playstore-release.aab
Normal file
BIN
playstore/release/monocles chat-1.5.2null-playstore-release.aab
Normal file
Binary file not shown.
|
@ -112,19 +112,16 @@ public class StartUI extends PermissionsActivity
|
|||
|
||||
@Override
|
||||
protected Void doInBackground(Void... params) {
|
||||
DatabaseBackend mDatabaseBackend = DatabaseBackend.getInstance(getBaseContext());
|
||||
Log.d(Config.LOGTAG, "Optimizing database");
|
||||
final Stopwatch stopwatch = Stopwatch.createStarted();
|
||||
try {
|
||||
try (DatabaseBackend mDatabaseBackend = DatabaseBackend.getInstance(getBaseContext())) {
|
||||
Log.d(Config.LOGTAG, "Optimizing database");
|
||||
final Stopwatch stopwatch = Stopwatch.createStarted();
|
||||
final SQLiteDatabase db = mDatabaseBackend.getWritableDatabase();
|
||||
db.execSQL("ANALYZE");
|
||||
db.execSQL("VACUUM");
|
||||
//db.execSQL("VACUUM"); // todo should we do it?
|
||||
db.execSQL("PRAGMA optimize");
|
||||
Log.d(Config.LOGTAG, String.format("Optimized database in %s", stopwatch.stop()));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
mDatabaseBackend.close();
|
||||
}
|
||||
return null;
|
||||
}
|
|
@ -64,6 +64,12 @@
|
|||
|
||||
<!-- OpenKeyChain -->
|
||||
<package android:name="org.sufficientlysecure.keychain"/>
|
||||
<intent>
|
||||
<action android:name="eu.siacs.conversations.location.request"/>
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="eu.siacs.conversations.location.show"/>
|
||||
</intent>
|
||||
</queries>
|
||||
|
||||
|
||||
|
|
|
@ -109,7 +109,7 @@ public final class Config {
|
|||
|
||||
public static final boolean XEP_0392 = true; //enables XEP-0392 v0.6.0
|
||||
|
||||
public static final int VIDEO_FAST_UPLOAD_SIZE = 5 * 1024 * 1024;
|
||||
public static final int VIDEO_FAST_UPLOAD_SIZE = 10 * 1024 * 1024;
|
||||
|
||||
public static final int AVATAR_SIZE = 480;
|
||||
public static final Bitmap.CompressFormat AVATAR_FORMAT = Bitmap.CompressFormat.JPEG;
|
||||
|
|
|
@ -22,6 +22,7 @@ import java.util.regex.Matcher;
|
|||
import java.util.regex.Pattern;
|
||||
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
|
||||
import eu.siacs.conversations.crypto.axolotl.FingerprintStatus;
|
||||
import eu.siacs.conversations.http.URL;
|
||||
import eu.siacs.conversations.services.AvatarService;
|
||||
|
@ -1034,7 +1035,8 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
|
|||
}
|
||||
|
||||
public boolean isTrusted() {
|
||||
FingerprintStatus s = conversation.getAccount().getAxolotlService().getFingerprintTrust(axolotlFingerprint);
|
||||
final AxolotlService axolotlService = conversation.getAccount().getAxolotlService();
|
||||
final FingerprintStatus s = axolotlService != null ? axolotlService.getFingerprintTrust(axolotlFingerprint) : null;
|
||||
return s != null && s.isTrusted();
|
||||
}
|
||||
|
||||
|
|
|
@ -180,6 +180,7 @@ public class IqGenerator extends AbstractGenerator {
|
|||
info.setAttribute("type", avatar.type);
|
||||
return publish("urn:xmpp:avatar:metadata", item, options);
|
||||
}
|
||||
|
||||
public IqPacket deleteAvatar() {
|
||||
final Element item = new Element("item");
|
||||
item.addChild("metadata", "urn:xmpp:avatar:metadata");
|
||||
|
@ -461,16 +462,20 @@ public class IqGenerator extends AbstractGenerator {
|
|||
final IqPacket packet = new IqPacket(IqPacket.TYPE.SET);
|
||||
packet.setTo(appServer);
|
||||
final Element command = packet.addChild("command", Namespace.COMMANDS);
|
||||
command.setAttribute("node", "register-push-fcm");
|
||||
command.setAttribute("node", "v1-register-push");
|
||||
command.setAttribute("action", "execute");
|
||||
final Data data = new Data();
|
||||
data.put("type", "fcm");
|
||||
data.put("token", token);
|
||||
data.put("android-id", deviceId);
|
||||
if (muc != null) {
|
||||
data.put("node", deviceId);
|
||||
data.put("FORM_TYPE", "https://github.com/tmolitor-stud-tu/mod_push_appserver/#v1-register-push");
|
||||
// to do MUC not support
|
||||
/*if (muc != null) {
|
||||
data.put("muc", muc.toEscapedString());
|
||||
}
|
||||
}*/
|
||||
data.submit();
|
||||
command.addChild(data);
|
||||
Log.d(Config.LOGTAG, "Push packet " + packet);
|
||||
return packet;
|
||||
}
|
||||
|
||||
|
@ -481,8 +486,11 @@ public class IqGenerator extends AbstractGenerator {
|
|||
command.setAttribute("node", "unregister-push-fcm");
|
||||
command.setAttribute("action", "execute");
|
||||
final Data data = new Data();
|
||||
data.put("type", "fcm");
|
||||
data.put("node", deviceId);
|
||||
data.put("channel", channel);
|
||||
data.put("android-id", deviceId);
|
||||
data.put("FORM_TYPE", "https://github.com/tmolitor-stud-tu/mod_push_appserver/#v1-unregister-push");
|
||||
data.submit();
|
||||
command.addChild(data);
|
||||
return packet;
|
||||
|
|
|
@ -117,11 +117,20 @@ public class HttpDownloadConnection implements Transferable {
|
|||
if (this.message.getEncryption() == Message.ENCRYPTION_AXOLOTL && this.file.getKey() == null) {
|
||||
this.message.setEncryption(Message.ENCRYPTION_NONE);
|
||||
}
|
||||
//TODO add auth tag size to knownFileSize
|
||||
final Long knownFileSize = message.getFileParams().size;
|
||||
final Long knownFileSize;
|
||||
if (message.getEncryption() == Message.ENCRYPTION_PGP || message.getEncryption() == Message.ENCRYPTION_DECRYPTED) {
|
||||
knownFileSize = null;
|
||||
} else {
|
||||
knownFileSize = message.getFileParams().size;
|
||||
}
|
||||
Log.d(Config.LOGTAG, "knownFileSize: " + knownFileSize + ", body=" + message.getBody());
|
||||
if (knownFileSize != null && interactive) {
|
||||
this.file.setExpectedSize(knownFileSize);
|
||||
if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL
|
||||
&& this.file.getKey() != null) {
|
||||
this.file.setExpectedSize(knownFileSize + 16);
|
||||
} else {
|
||||
this.file.setExpectedSize(knownFileSize);
|
||||
}
|
||||
download(true);
|
||||
} else {
|
||||
checkFileSize(interactive);
|
||||
|
@ -235,6 +244,8 @@ public class HttpDownloadConnection implements Transferable {
|
|||
mXmppConnectionService.showErrorToastInUi(R.string.download_failed_could_not_connect);
|
||||
} else if (e instanceof FileWriterException) {
|
||||
mXmppConnectionService.showErrorToastInUi(R.string.download_failed_could_not_write_file);
|
||||
} else if (e instanceof InvalidFileException) {
|
||||
mXmppConnectionService.showErrorToastInUi(R.string.download_failed_invalid_file);
|
||||
} else {
|
||||
mXmppConnectionService.showErrorToastInUi(R.string.download_failed_file_not_found);
|
||||
}
|
||||
|
@ -330,6 +341,7 @@ public class HttpDownloadConnection implements Transferable {
|
|||
);
|
||||
final Request request = new Request.Builder()
|
||||
.url(URL.stripFragment(mUrl))
|
||||
.addHeader("Accept-Encoding", "identity")
|
||||
.head()
|
||||
.build();
|
||||
mostRecentCall = client.newCall(request);
|
||||
|
@ -355,11 +367,11 @@ public class HttpDownloadConnection implements Transferable {
|
|||
throw new IOException("Server reported negative file size");
|
||||
}
|
||||
return size;
|
||||
} catch (IOException e) {
|
||||
} catch (final IOException e) {
|
||||
Log.d(Config.LOGTAG, "io exception during HEAD " + e.getMessage());
|
||||
throw e;
|
||||
} catch (NumberFormatException e) {
|
||||
throw new IOException();
|
||||
} catch (final NumberFormatException e) {
|
||||
throw new IOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -448,9 +460,12 @@ public class HttpDownloadConnection implements Transferable {
|
|||
transmitted += count;
|
||||
try {
|
||||
outputStream.write(buffer, 0, count);
|
||||
} catch (IOException e) {
|
||||
} catch (final IOException e) {
|
||||
throw new FileWriterException(file);
|
||||
}
|
||||
if (transmitted > expected) {
|
||||
throw new InvalidFileException(String.format("File exceeds expected size of %d", expected));
|
||||
}
|
||||
updateProgress(Math.round(((double) transmitted / expected) * 100));
|
||||
}
|
||||
outputStream.flush();
|
||||
|
@ -478,4 +493,12 @@ public class HttpDownloadConnection implements Transferable {
|
|||
throw new IOException(String.format(Locale.ENGLISH, "HTTP Status code was %d", code));
|
||||
}
|
||||
}
|
||||
|
||||
private static class InvalidFileException extends IOException {
|
||||
|
||||
private InvalidFileException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -14,6 +14,15 @@ import java.util.Map;
|
|||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import android.os.Build;
|
||||
import android.text.Html;
|
||||
|
||||
import net.java.otr4j.session.Session;
|
||||
import net.java.otr4j.session.SessionStatus;
|
||||
import eu.siacs.conversations.crypto.OtrService;
|
||||
import eu.siacs.conversations.entities.Presence;
|
||||
import eu.siacs.conversations.entities.ServiceDiscoveryResult;
|
||||
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.R;
|
||||
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
|
||||
|
@ -52,6 +61,8 @@ import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
|
|||
|
||||
public class MessageParser extends AbstractParser implements OnMessagePacketReceived {
|
||||
|
||||
private static final List<String> CLIENTS_SENDING_HTML_IN_OTR = Arrays.asList("Pidgin", "Adium", "Trillian");
|
||||
|
||||
private static final SimpleDateFormat TIME_FORMAT = new SimpleDateFormat("HH:mm:ss", Locale.ENGLISH);
|
||||
|
||||
private static final List<String> JINGLE_MESSAGE_ELEMENT_NAMES = Arrays.asList("accept", "propose", "proceed", "reject", "retract");
|
||||
|
@ -96,6 +107,31 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
|
|||
return result != null ? result : fallback;
|
||||
}
|
||||
|
||||
private static boolean clientMightSendHtml(Account account, Jid from) {
|
||||
String resource = from.getResource();
|
||||
if (resource == null) {
|
||||
return false;
|
||||
}
|
||||
Presence presence = account.getRoster().getContact(from).getPresences().getPresencesMap().get(resource);
|
||||
ServiceDiscoveryResult disco = presence == null ? null : presence.getServiceDiscoveryResult();
|
||||
if (disco == null) {
|
||||
return false;
|
||||
}
|
||||
return hasIdentityKnowForSendingHtml(disco.getIdentities());
|
||||
}
|
||||
|
||||
private static boolean hasIdentityKnowForSendingHtml(List<ServiceDiscoveryResult.Identity> identities) {
|
||||
for (ServiceDiscoveryResult.Identity identity : identities) {
|
||||
if (identity.getName() != null) {
|
||||
if (CLIENTS_SENDING_HTML_IN_OTR.contains(identity.getName())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private boolean extractChatState(Conversation c, final boolean isTypeGroupChat, final MessagePacket packet) {
|
||||
ChatState state = ChatState.parse(packet);
|
||||
if (state != null && c != null) {
|
||||
|
@ -127,6 +163,67 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
|
|||
return false;
|
||||
}
|
||||
|
||||
private Message parseOtrChat(String body, Jid from, String id, Conversation conversation) {
|
||||
String presence;
|
||||
if (from.isBareJid()) {
|
||||
presence = "";
|
||||
} else {
|
||||
presence = from.getResource();
|
||||
}
|
||||
if (body.matches("^\\?OTRv\\d{1,2}\\?.*")) {
|
||||
conversation.endOtrIfNeeded();
|
||||
}
|
||||
if (!conversation.hasValidOtrSession()) {
|
||||
conversation.startOtrSession(presence, false);
|
||||
} else {
|
||||
String foreignPresence = conversation.getOtrSession().getSessionID().getUserID();
|
||||
if (!foreignPresence.equals(presence)) {
|
||||
conversation.endOtrIfNeeded();
|
||||
conversation.startOtrSession(presence, false);
|
||||
}
|
||||
}
|
||||
try {
|
||||
conversation.setLastReceivedOtrMessageId(id);
|
||||
Session otrSession = conversation.getOtrSession();
|
||||
body = otrSession.transformReceiving(body);
|
||||
SessionStatus status = otrSession.getSessionStatus();
|
||||
if (body == null && status == SessionStatus.ENCRYPTED) {
|
||||
mXmppConnectionService.onOtrSessionEstablished(conversation);
|
||||
return null;
|
||||
} else if (body == null && status == SessionStatus.FINISHED) {
|
||||
conversation.resetOtrSession();
|
||||
mXmppConnectionService.updateConversationUi();
|
||||
return null;
|
||||
} else if (body == null || (body.isEmpty())) {
|
||||
return null;
|
||||
}
|
||||
if (body.startsWith(CryptoHelper.FILETRANSFER)) {
|
||||
String key = body.substring(CryptoHelper.FILETRANSFER.length());
|
||||
conversation.setSymmetricKey(CryptoHelper.hexToBytes(key));
|
||||
return null;
|
||||
}
|
||||
if (clientMightSendHtml(conversation.getAccount(), from)) {
|
||||
Log.d(Config.LOGTAG, conversation.getAccount().getJid().asBareJid() + ": received OTR message from bad behaving client. escaping HTML…");
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
body = Html.fromHtml(body, Html.FROM_HTML_MODE_LEGACY).toString();
|
||||
} else {
|
||||
body = Html.fromHtml(body).toString();
|
||||
}
|
||||
}
|
||||
|
||||
final OtrService otrService = conversation.getAccount().getOtrService();
|
||||
Message finishedMessage = new Message(conversation, body, Message.ENCRYPTION_OTR, Message.STATUS_RECEIVED);
|
||||
finishedMessage.setFingerprint(otrService.getFingerprint(otrSession.getRemotePublicKey()));
|
||||
conversation.setLastReceivedOtrMessageId(null);
|
||||
|
||||
return finishedMessage;
|
||||
} catch (Exception e) {
|
||||
conversation.resetOtrSession();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
|
@ -354,6 +451,15 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (message != null) {
|
||||
if (message.getEncryption() == Message.ENCRYPTION_OTR) {
|
||||
Conversation conversation = (Conversation) message.getConversation();
|
||||
conversation.endOtrIfNeeded();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -521,7 +627,20 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
|
|||
}
|
||||
}
|
||||
final Message message;
|
||||
if (pgpEncrypted != null && Config.supportOpenPgp()) {
|
||||
if (body != null && body.content.startsWith("?OTR") && Config.supportOtr()) {
|
||||
if (!isForwarded && !isTypeGroupChat && isProperlyAddressed && !conversationMultiMode) {
|
||||
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.content, Message.ENCRYPTION_NONE, status);
|
||||
if (body.count > 1) {
|
||||
message.setBodyLanguage(body.language);
|
||||
}
|
||||
}
|
||||
} else if (pgpEncrypted != null && Config.supportOpenPgp()) {
|
||||
message = new Message(conversation, pgpEncrypted, Message.ENCRYPTION_PGP, status);
|
||||
} else if (axolotlEncrypted != null && Config.supportOmemo()) {
|
||||
Jid origin;
|
||||
|
@ -835,6 +954,13 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece
|
|||
processMessageReceipts(account, packet, remoteMsgId, query);
|
||||
}
|
||||
|
||||
if (message.getStatus() == Message.STATUS_RECEIVED
|
||||
&& conversation.getOtrSession() != null
|
||||
&& !conversation.getOtrSession().getSessionID().getUserID()
|
||||
.equals(message.getCounterpart().getResource())) {
|
||||
conversation.endOtrIfNeeded();
|
||||
}
|
||||
|
||||
mXmppConnectionService.databaseBackend.createMessage(message);
|
||||
final HttpConnectionManager manager = this.mXmppConnectionService.getHttpConnectionManager();
|
||||
if ((mXmppConnectionService.easyDownloader() || message.trusted()) && message.treatAsDownloadable() && manager.getAutoAcceptFileSize() > 0) {
|
||||
|
|
|
@ -746,7 +746,7 @@ public class FileBackend {
|
|||
if (cursor != null && cursor.moveToFirst()) {
|
||||
filename = cursor.getString(0);
|
||||
}
|
||||
} catch (final SecurityException | IllegalArgumentException e) {
|
||||
} catch (final Exception e) {
|
||||
filename = null;
|
||||
}
|
||||
if (filename == null) {
|
||||
|
|
|
@ -43,6 +43,7 @@ import androidx.appcompat.app.AppCompatActivity;
|
|||
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.base.Joiner;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import com.google.common.io.CharStreams;
|
||||
|
||||
import org.json.JSONArray;
|
||||
|
@ -77,6 +78,7 @@ import javax.net.ssl.TrustManager;
|
|||
import javax.net.ssl.TrustManagerFactory;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.R;
|
||||
import eu.siacs.conversations.crypto.XmppDomainVerifier;
|
||||
import eu.siacs.conversations.entities.MTMDecision;
|
||||
|
@ -391,13 +393,13 @@ public class MemorizingTrustManager {
|
|||
final List<String> fingerprints = getPoshFingerprints(domain);
|
||||
if (hash != null && fingerprints.size() > 0) {
|
||||
if (fingerprints.contains(hash)) {
|
||||
Log.d("mtm", "trusted cert fingerprint of " + domain + " via posh");
|
||||
Log.d(Config.LOGTAG, "trusted cert fingerprint of " + domain + " via posh");
|
||||
return;
|
||||
} else {
|
||||
Log.d("mtm", "fingerprint " + hash + " not found in " + fingerprints);
|
||||
Log.d(Config.LOGTAG, "fingerprint " + hash + " not found in " + fingerprints);
|
||||
}
|
||||
if (getPoshCacheFile(domain).delete()) {
|
||||
Log.d("mtm", "deleted posh file for " + domain + " after not being able to verify");
|
||||
Log.d(Config.LOGTAG, "deleted posh file for " + domain + " after not being able to verify");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -410,7 +412,7 @@ public class MemorizingTrustManager {
|
|||
}
|
||||
}
|
||||
|
||||
private List<String> getPoshFingerprints(String domain) {
|
||||
private List<String> getPoshFingerprints(final String domain) {
|
||||
final List<String> cached = getPoshFingerprintsFromCache(domain);
|
||||
if (cached == null) {
|
||||
return getPoshFingerprintsFromServer(domain);
|
||||
|
@ -424,13 +426,13 @@ public class MemorizingTrustManager {
|
|||
}
|
||||
|
||||
private List<String> getPoshFingerprintsFromServer(String domain, String url, int maxTtl, boolean followUrl) {
|
||||
Log.d("mtm", "downloading json for " + domain + " from " + url);
|
||||
Log.d(Config.LOGTAG, "downloading json for " + domain + " from " + url);
|
||||
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(master);
|
||||
final boolean useTor = QuickConversationsService.isConversations() && preferences.getBoolean("use_tor", master.getResources().getBoolean(R.bool.use_tor));
|
||||
try {
|
||||
final List<String> results = new ArrayList<>();
|
||||
final InputStream inputStream = HttpConnectionManager.open(url, useTor);
|
||||
final String body = CharStreams.toString(new InputStreamReader(inputStream, Charsets.UTF_8));
|
||||
final String body = CharStreams.toString(new InputStreamReader(ByteStreams.limit(inputStream,10_000), Charsets.UTF_8));
|
||||
final JSONObject jsonObject = new JSONObject(body);
|
||||
int expires = jsonObject.getInt("expires");
|
||||
if (expires <= 0) {
|
||||
|
@ -457,7 +459,7 @@ public class MemorizingTrustManager {
|
|||
writeFingerprintsToCache(domain, results, 1000L * expires + System.currentTimeMillis());
|
||||
return results;
|
||||
} catch (final Exception e) {
|
||||
Log.d("mtm", "error fetching posh " + e.getMessage());
|
||||
Log.d(Config.LOGTAG, "error fetching posh",e);
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
|
@ -495,7 +497,7 @@ public class MemorizingTrustManager {
|
|||
file.delete();
|
||||
return null;
|
||||
} else {
|
||||
Log.d("mtm", "posh fingerprints expire in " + (expiresIn / 1000) + "s");
|
||||
Log.d(Config.LOGTAG, "posh fingerprints expire in " + (expiresIn / 1000) + "s");
|
||||
}
|
||||
final List<String> result = new ArrayList<>();
|
||||
final JSONArray jsonArray = jsonObject.getJSONArray("fingerprints");
|
||||
|
@ -512,7 +514,6 @@ public class MemorizingTrustManager {
|
|||
}
|
||||
|
||||
private X509Certificate[] getAcceptedIssuers() {
|
||||
LOGGER.log(Level.FINE, "getAcceptedIssuers()");
|
||||
return defaultTrustManager == null ? new X509Certificate[0] : defaultTrustManager.getAcceptedIssuers();
|
||||
}
|
||||
|
||||
|
|
|
@ -258,7 +258,11 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded {
|
|||
//do nothing
|
||||
} else {
|
||||
Log.d(Config.LOGTAG, a.getJid().asBareJid().toString() + ": error executing mam: " + p.toString());
|
||||
finalizeQuery(query, true);
|
||||
try {
|
||||
finalizeQuery(query, true);
|
||||
} catch (final IllegalStateException e) {
|
||||
//ignored
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
|
|
|
@ -98,6 +98,8 @@ public class NotificationService {
|
|||
|
||||
private static final String MESSAGES_GROUP = "eu.siacs.conversations.messages";
|
||||
private static final String MISSED_CALLS_GROUP = "eu.siacs.conversations.missed_calls";
|
||||
private static final int MESSAGE_DAT = 70;
|
||||
private static final long[] MESSAGE_PATTERN = {0, 3 * MESSAGE_DAT, MESSAGE_DAT, MESSAGE_DAT};
|
||||
private static final int NOTIFICATION_ID_MULTIPLIER = 1024 * 1024;
|
||||
public static final int NOTIFICATION_ID = 2 * NOTIFICATION_ID_MULTIPLIER;
|
||||
public static final int FOREGROUND_NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 4;
|
||||
|
@ -109,6 +111,7 @@ public class NotificationService {
|
|||
public static final int MISSED_CALL_NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 14;
|
||||
public static final int IMPORT_BACKUP_NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 16;
|
||||
public static final int EXPORT_BACKUP_NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 18;
|
||||
public static final int UPDATE_NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 20;
|
||||
private final XmppConnectionService mXmppConnectionService;
|
||||
private final LinkedHashMap<String, ArrayList<Message>> notifications = new LinkedHashMap<>();
|
||||
private final LinkedHashMap<Conversational, MissedCallsInfo> mMissedCalls = new LinkedHashMap<>();
|
||||
|
@ -268,14 +271,18 @@ public class NotificationService {
|
|||
// create individual notification channels for selected chats
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
private void createIndividualNotificationChannels(final NotificationManager notificationManager) {
|
||||
final int chats = mXmppConnectionService.getConversations().size();
|
||||
for (int i = 0; i < chats; i++) {
|
||||
if (mXmppConnectionService.hasIndividualNotification(mXmppConnectionService.getConversations().get(i))) {
|
||||
if (mXmppConnectionService.getConversations().get(i).getMode() == Conversation.MODE_SINGLE) {
|
||||
createCallNotificationChannels(notificationManager, i);
|
||||
try {
|
||||
final int chats = mXmppConnectionService.getConversations().size();
|
||||
for (int i = 0; i < chats; i++) {
|
||||
if (mXmppConnectionService.hasIndividualNotification(mXmppConnectionService.getConversations().get(i))) {
|
||||
if (mXmppConnectionService.getConversations().get(i).getMode() == Conversation.MODE_SINGLE) {
|
||||
createCallNotificationChannels(notificationManager, i);
|
||||
}
|
||||
createMessageNotificationChannels(notificationManager, i);
|
||||
}
|
||||
createMessageNotificationChannels(notificationManager, i);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -327,9 +334,7 @@ public class NotificationService {
|
|||
.setUsage(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT)
|
||||
.build());
|
||||
messagesChannel.setLightColor(LED_COLOR);
|
||||
final int dat = 70;
|
||||
final long[] pattern = {0, 3 * dat, dat, dat};
|
||||
messagesChannel.setVibrationPattern(pattern);
|
||||
messagesChannel.setVibrationPattern(MESSAGE_PATTERN);
|
||||
messagesChannel.enableVibration(true);
|
||||
messagesChannel.enableLights(true);
|
||||
notificationManager.createNotificationChannelGroup(new NotificationChannelGroup(INDIVIDUAL_NOTIFICATION_PREFIX + name + uuid, name + " (" + jid + ")"));
|
||||
|
@ -354,9 +359,7 @@ public class NotificationService {
|
|||
.setUsage(AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT)
|
||||
.build());
|
||||
messagesChannel.setLightColor(LED_COLOR);
|
||||
final int dat = 70;
|
||||
final long[] pattern = {0, 3 * dat, dat, dat};
|
||||
messagesChannel.setVibrationPattern(pattern);
|
||||
messagesChannel.setVibrationPattern(MESSAGE_PATTERN);
|
||||
messagesChannel.enableVibration(true);
|
||||
messagesChannel.enableLights(true);
|
||||
messagesChannel.setGroup("chats");
|
||||
|
@ -463,16 +466,20 @@ public class NotificationService {
|
|||
// clean all individual notification settings
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
public void cleanAllNotificationChannels(final Context context) {
|
||||
final NotificationManager notificationManager = context.getSystemService(NotificationManager.class);
|
||||
final int chats = mXmppConnectionService.getConversations().size();
|
||||
for (int i = 0; i < chats; i++) {
|
||||
if (mXmppConnectionService.hasIndividualNotification(mXmppConnectionService.getConversations().get(i))) {
|
||||
final String uuid = mXmppConnectionService.getConversations().get(i).getUuid();
|
||||
mXmppConnectionService.setIndividualNotificationPreference(mXmppConnectionService.getConversations().get(i), true);
|
||||
cleanCallNotificationChannels(notificationManager, uuid);
|
||||
cleanMessageNotificationChannels(notificationManager, uuid);
|
||||
cleanNotificationGroup(notificationManager, uuid);
|
||||
try {
|
||||
final NotificationManager notificationManager = context.getSystemService(NotificationManager.class);
|
||||
final int chats = mXmppConnectionService.getConversations().size();
|
||||
for (int i = 0; i < chats; i++) {
|
||||
if (mXmppConnectionService.hasIndividualNotification(mXmppConnectionService.getConversations().get(i))) {
|
||||
final String uuid = mXmppConnectionService.getConversations().get(i).getUuid();
|
||||
mXmppConnectionService.setIndividualNotificationPreference(mXmppConnectionService.getConversations().get(i), true);
|
||||
cleanCallNotificationChannels(notificationManager, uuid);
|
||||
cleanMessageNotificationChannels(notificationManager, uuid);
|
||||
cleanNotificationGroup(notificationManager, uuid);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -488,10 +495,6 @@ public class NotificationService {
|
|||
notificationManager.deleteNotificationChannel(channelID);
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
try {
|
||||
final int groups = notificationManager.getNotificationChannelGroups().size();
|
||||
for (int i2 = 0; i2 < groups; i2++) {
|
||||
final String groupID = notificationManager.getNotificationChannelGroups().get(i2).getId();
|
||||
|
@ -987,9 +990,7 @@ public class NotificationService {
|
|||
final boolean headsup = preferences.getBoolean("notification_headsup", resources.getBoolean(R.bool.headsup_notifications));
|
||||
if (notify && !quietHours) {
|
||||
if (vibrate) {
|
||||
final int dat = 70;
|
||||
final long[] pattern = {0, 3 * dat, dat, dat};
|
||||
mBuilder.setVibrate(pattern);
|
||||
mBuilder.setVibrate(MESSAGE_PATTERN);
|
||||
} else {
|
||||
mBuilder.setVibrate(new long[]{0});
|
||||
}
|
||||
|
@ -1256,7 +1257,7 @@ public class NotificationService {
|
|||
mBuilder.addAction(snoozeAction);
|
||||
++addedActionsCount;
|
||||
}
|
||||
if (addedActionsCount < 3 && mXmppConnectionService.webViewAvailable()) {
|
||||
if (addedActionsCount < 3) {
|
||||
final Message firstLocationMessage = getFirstLocationMessage(messages);
|
||||
if (firstLocationMessage != null) {
|
||||
final PendingIntent pendingShowLocationIntent = createShowLocationIntent(firstLocationMessage);
|
||||
|
@ -1772,7 +1773,7 @@ public class NotificationService {
|
|||
}
|
||||
|
||||
public void AppUpdateServiceNotification(Notification notification) {
|
||||
notify(FOREGROUND_NOTIFICATION_ID, notification);
|
||||
notify(UPDATE_NOTIFICATION_ID, notification);
|
||||
}
|
||||
|
||||
private void notify(String tag, int id, Notification notification) {
|
||||
|
|
|
@ -6,6 +6,7 @@ import android.util.Log;
|
|||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
|
@ -76,12 +77,12 @@ public class ProviderService extends AsyncTask<String, Object, Boolean> {
|
|||
String ratingC2S = null;
|
||||
String ratingS2S = null;
|
||||
int ratingXmppComplianceTester = 0;
|
||||
final String provider = jsonObject.names().getString(i);
|
||||
final String provider = Objects.requireNonNull(jsonObject.names()).getString(i);
|
||||
if (provider.length() > 0) {
|
||||
for (int ii = 0; ii < jsonObject.length(); ii++) {
|
||||
final JSONObject json = new JSONObject(jsonObject.get(provider).toString());
|
||||
String featureName = json.names().getString(ii);
|
||||
final JSONObject subjson = new JSONObject(json.get(json.names().getString(ii)).toString());
|
||||
final JSONObject json = new JSONObject(jsonObject.get(provider).toString());
|
||||
for (int ii = 0; ii < json.length(); ii++) {
|
||||
String featureName = Objects.requireNonNull(json.names()).getString(ii);
|
||||
final JSONObject subjson = new JSONObject(json.get(Objects.requireNonNull(json.names()).getString(ii)).toString());
|
||||
if (featureName.equals("inBandRegistration")) {
|
||||
inBandRegistration = subjson.getBoolean("content");
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ import static eu.siacs.conversations.utils.RichPreview.RICH_LINK_METADATA;
|
|||
import static eu.siacs.conversations.utils.StorageHelper.getAppMediaDirectory;
|
||||
|
||||
import android.Manifest;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.TargetApi;
|
||||
import android.app.AlarmManager;
|
||||
|
@ -59,6 +60,13 @@ import android.util.DisplayMetrics;
|
|||
import android.util.Log;
|
||||
import android.util.LruCache;
|
||||
import android.util.Pair;
|
||||
import net.java.otr4j.OtrException;
|
||||
import net.java.otr4j.session.Session;
|
||||
import net.java.otr4j.session.SessionID;
|
||||
import net.java.otr4j.session.SessionImpl;
|
||||
import net.java.otr4j.session.SessionStatus;
|
||||
import eu.siacs.conversations.xmpp.jid.OtrJidHelper;
|
||||
import eu.siacs.conversations.xmpp.Jid;
|
||||
|
||||
import androidx.annotation.BoolRes;
|
||||
import androidx.annotation.IntegerRes;
|
||||
|
@ -259,9 +267,18 @@ public class XmppConnectionService extends Service {
|
|||
Conversation conversation = find(getConversations(), contact);
|
||||
if (conversation != null) {
|
||||
if (online) {
|
||||
conversation.endOtrIfNeeded();
|
||||
if (contact.getPresences().size() == 1) {
|
||||
sendUnsentMessages(conversation);
|
||||
}
|
||||
} else {
|
||||
//check if the resource we are haveing a conversation with is still online
|
||||
if (conversation.hasValidOtrSession()) {
|
||||
String otrResource = conversation.getOtrSession().getSessionID().getUserID();
|
||||
if (!(Arrays.asList(contact.getPresences().toResourceArray()).contains(otrResource))) {
|
||||
conversation.endOtrIfNeeded();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -442,6 +459,9 @@ public class XmppConnectionService extends Service {
|
|||
if (conversation.getAccount() == account
|
||||
&& !pendingJoin
|
||||
&& !inProgressJoin) {
|
||||
if (!conversation.startOtrIfNeeded()) {
|
||||
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": couldn't start OTR with " + conversation.getContact().getJid() + " when needed");
|
||||
}
|
||||
sendUnsentMessages(conversation);
|
||||
}
|
||||
}
|
||||
|
@ -1370,7 +1390,9 @@ public class XmppConnectionService extends Service {
|
|||
@Override
|
||||
public void onCreate() {
|
||||
updateNotificationChannels();
|
||||
cleanOldNotificationChannels();
|
||||
if (Compatibility.runsTwentySix()) {
|
||||
cleanOldNotificationChannels();
|
||||
}
|
||||
mChannelDiscoveryService.initializeMuclumbusService();
|
||||
mForceDuringOnCreate.set(Compatibility.runsAndTargetsTwentySix(this));
|
||||
toggleForegroundService();
|
||||
|
@ -1471,7 +1493,7 @@ public class XmppConnectionService extends Service {
|
|||
new Thread(mNotificationService::updateChannels).start();
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.O)
|
||||
public void cleanOldNotificationChannels() {
|
||||
new Thread(() -> {
|
||||
try {
|
||||
|
@ -1783,6 +1805,11 @@ public class XmppConnectionService extends Service {
|
|||
databaseBackend.updateConversation(conversation);
|
||||
}
|
||||
}
|
||||
if (!resend && message.getEncryption() != Message.ENCRYPTION_OTR) {
|
||||
conversation.endOtrIfNeeded();
|
||||
conversation.findUnsentMessagesWithEncryption(Message.ENCRYPTION_OTR,
|
||||
message1 -> markMessage(message1, Message.STATUS_SEND_FAILED));
|
||||
}
|
||||
|
||||
final boolean inProgressJoin = isJoinInProgress(conversation);
|
||||
|
||||
|
@ -1815,6 +1842,30 @@ public class XmppConnectionService extends Service {
|
|||
packet = mMessageGenerator.generatePgpChat(message);
|
||||
}
|
||||
break;
|
||||
case Message.ENCRYPTION_OTR:
|
||||
SessionImpl otrSession = conversation.getOtrSession();
|
||||
if (otrSession != null && otrSession.getSessionStatus() == SessionStatus.ENCRYPTED) {
|
||||
try {
|
||||
message.setCounterpart(OtrJidHelper.fromSessionID(otrSession.getSessionID()));
|
||||
} catch (IllegalArgumentException e) {
|
||||
break;
|
||||
}
|
||||
if (message.needsUploading()) {
|
||||
mJingleConnectionManager.startJingleFileTransfer(message);
|
||||
} else {
|
||||
packet = mMessageGenerator.generateOtrChat(message);
|
||||
}
|
||||
} else if (otrSession == null) {
|
||||
if (message.fixCounterpart()) {
|
||||
conversation.startOtrSession(message.getCounterpart().getResource(), true);
|
||||
} else {
|
||||
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": could not fix counterpart for OTR message to contact " + message.getCounterpart());
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
Log.d(Config.LOGTAG, account.getJid().asBareJid() + " OTR session with " + message.getContact() + " is in wrong state: " + otrSession.getSessionStatus().toString());
|
||||
}
|
||||
break;
|
||||
case Message.ENCRYPTION_AXOLOTL:
|
||||
message.setFingerprint(account.getAxolotlService().getOwnFingerprint());
|
||||
if (message.needsUploading()) {
|
||||
|
@ -1867,6 +1918,12 @@ public class XmppConnectionService extends Service {
|
|||
}
|
||||
}
|
||||
break;
|
||||
case Message.ENCRYPTION_OTR:
|
||||
if (!conversation.hasValidOtrSession() && message.getCounterpart() != null) {
|
||||
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": create otr session without starting for " + message.getContact().getJid());
|
||||
conversation.startOtrSession(message.getCounterpart().getResource(), false);
|
||||
}
|
||||
break;
|
||||
case Message.ENCRYPTION_AXOLOTL:
|
||||
message.setFingerprint(account.getAxolotlService().getOwnFingerprint());
|
||||
break;
|
||||
|
@ -2093,7 +2150,9 @@ public class XmppConnectionService extends Service {
|
|||
public void createBookmark(final Account account, final Bookmark bookmark) {
|
||||
account.putBookmark(bookmark);
|
||||
final XmppConnection connection = account.getXmppConnection();
|
||||
if (connection != null && connection.getFeatures().bookmarks2()) {
|
||||
if (connection == null) {
|
||||
Log.d(Config.LOGTAG, account.getJid().asBareJid()+": no connection. ignoring bookmark creation");
|
||||
} else if (connection != null && connection.getFeatures().bookmarks2()) {
|
||||
final Element item = mIqGenerator.publishBookmarkItem(bookmark);
|
||||
pushNodeAndEnforcePublishOptions(account, Namespace.BOOKMARKS2, item, bookmark.getJid().asBareJid().toEscapedString(), PublishOptions.persistentWhitelistAccessMaxItems());
|
||||
} else if (connection != null && connection.getFeatures().bookmarksConversion()) {
|
||||
|
@ -2588,7 +2647,7 @@ public class XmppConnectionService extends Service {
|
|||
getNotificationService().clear(conversation);
|
||||
conversation.setStatus(Conversation.STATUS_ARCHIVED);
|
||||
conversation.setNextMessage(null);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
if (Compatibility.runsTwentySix()) {
|
||||
try {
|
||||
mNotificationService.cleanNotificationChannels(this, conversation.getUuid());
|
||||
} catch (Exception e) {
|
||||
|
@ -2776,7 +2835,7 @@ public class XmppConnectionService extends Service {
|
|||
for (final Conversation conversation : conversations) {
|
||||
if (conversation.getAccount() == account) {
|
||||
if (conversation.getMode() == Conversation.MODE_MULTI) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
if (Compatibility.runsTwentySix()) {
|
||||
try {
|
||||
mNotificationService.cleanNotificationChannels(this, conversation.getUuid());
|
||||
} catch (Exception e) {
|
||||
|
@ -3445,7 +3504,7 @@ public class XmppConnectionService extends Service {
|
|||
}
|
||||
|
||||
private void leaveMuc(Conversation conversation, boolean now) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
if (Compatibility.runsTwentySix()) {
|
||||
try {
|
||||
mNotificationService.cleanNotificationChannels(this, conversation.getUuid());
|
||||
} catch (Exception e) {
|
||||
|
@ -3771,6 +3830,12 @@ public class XmppConnectionService extends Service {
|
|||
if (conversation.getAccount() == account) {
|
||||
if (conversation.getMode() == Conversation.MODE_MULTI) {
|
||||
leaveMuc(conversation, true);
|
||||
} else {
|
||||
if (conversation.endOtrIfNeeded()) {
|
||||
Log.d(Config.LOGTAG, account.getJid().asBareJid()
|
||||
+ ": ended otr session with "
|
||||
+ conversation.getJid());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3827,6 +3892,65 @@ public class XmppConnectionService extends Service {
|
|||
}
|
||||
pushContactToServer(contact, preAuth);
|
||||
}
|
||||
public void onOtrSessionEstablished(Conversation conversation) {
|
||||
final Account account = conversation.getAccount();
|
||||
final Session otrSession = conversation.getOtrSession();
|
||||
Log.d(Config.LOGTAG,
|
||||
account.getJid().asBareJid() + " otr session established with "
|
||||
+ conversation.getJid() + "/"
|
||||
+ otrSession.getSessionID().getUserID());
|
||||
conversation.findUnsentMessagesWithEncryption(Message.ENCRYPTION_OTR, new Conversation.OnMessageFound() {
|
||||
|
||||
@Override
|
||||
public void onMessageFound(Message message) {
|
||||
SessionID id = otrSession.getSessionID();
|
||||
try {
|
||||
message.setCounterpart(Jid.of(id.getAccountID() + "/" + id.getUserID()));
|
||||
} catch (IllegalArgumentException e) {
|
||||
return;
|
||||
}
|
||||
if (message.needsUploading()) {
|
||||
mJingleConnectionManager.startJingleFileTransfer(message);
|
||||
} else {
|
||||
MessagePacket outPacket = mMessageGenerator.generateOtrChat(message);
|
||||
if (outPacket != null) {
|
||||
mMessageGenerator.addDelay(outPacket, message.getTimeSent());
|
||||
message.setStatus(Message.STATUS_SEND);
|
||||
databaseBackend.updateMessage(message, false);
|
||||
sendMessagePacket(account, outPacket);
|
||||
}
|
||||
}
|
||||
updateConversationUi();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public boolean renewSymmetricKey(Conversation conversation) {
|
||||
Account account = conversation.getAccount();
|
||||
byte[] symmetricKey = new byte[32];
|
||||
this.mRandom.nextBytes(symmetricKey);
|
||||
Session otrSession = conversation.getOtrSession();
|
||||
if (otrSession != null) {
|
||||
MessagePacket packet = new MessagePacket();
|
||||
packet.setType(MessagePacket.TYPE_CHAT);
|
||||
packet.setFrom(account.getJid());
|
||||
MessageGenerator.addMessageHints(packet);
|
||||
packet.setAttribute("to", otrSession.getSessionID().getAccountID() + "/"
|
||||
+ otrSession.getSessionID().getUserID());
|
||||
try {
|
||||
packet.setBody(otrSession
|
||||
.transformSending(CryptoHelper.FILETRANSFER
|
||||
+ CryptoHelper.bytesToHex(symmetricKey))[0]);
|
||||
sendMessagePacket(account, packet);
|
||||
conversation.setSymmetricKey(symmetricKey);
|
||||
return true;
|
||||
} catch (OtrException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public void pushContactToServer(final Contact contact) {
|
||||
pushContactToServer(contact, null);
|
||||
|
@ -4547,11 +4671,15 @@ public class XmppConnectionService extends Service {
|
|||
}
|
||||
|
||||
public void vibrate() {
|
||||
final boolean vibrateInChat = getBooleanPreference("vibrate_in_chat", R.bool.vibrate_in_chat);
|
||||
if (!isPhoneSilenced() && vibrateInChat) {
|
||||
Log.d(Config.LOGTAG, "Notification: short vibrate");
|
||||
Vibrator vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);
|
||||
vibrator.vibrate(100);
|
||||
try {
|
||||
final boolean vibrateInChat = getBooleanPreference("vibrate_in_chat", R.bool.vibrate_in_chat);
|
||||
if (!isPhoneSilenced() && vibrateInChat) {
|
||||
Log.d(Config.LOGTAG, "Notification: short vibrate");
|
||||
Vibrator vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);
|
||||
vibrator.vibrate(100);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5058,10 +5186,6 @@ public class XmppConnectionService extends Service {
|
|||
}
|
||||
}
|
||||
|
||||
public boolean webViewAvailable() {
|
||||
return this.getPackageManager().hasSystemFeature("android.software.webview");
|
||||
}
|
||||
|
||||
public void publishDisplayName(Account account) {
|
||||
String displayName = account.getDisplayName();
|
||||
final IqPacket request;
|
||||
|
@ -5204,7 +5328,10 @@ public class XmppConnectionService extends Service {
|
|||
boolean performedVerification = false;
|
||||
final AxolotlService axolotlService = contact.getAccount().getAxolotlService();
|
||||
for (XmppUri.Fingerprint fp : fingerprints) {
|
||||
if (fp.type == XmppUri.FingerprintType.OMEMO) {
|
||||
if (fp.type == XmppUri.FingerprintType.OTR) {
|
||||
performedVerification |= contact.addOtrFingerprint(fp.fingerprint);
|
||||
needsRosterWrite |= performedVerification;
|
||||
} else if (fp.type == XmppUri.FingerprintType.OMEMO) {
|
||||
String fingerprint = "05" + fp.fingerprint.replaceAll("\\s", "");
|
||||
FingerprintStatus fingerprintStatus = axolotlService.getFingerprintTrust(fingerprint);
|
||||
if (fingerprintStatus != null) {
|
||||
|
|
|
@ -163,6 +163,7 @@ import eu.siacs.conversations.xmpp.jingle.JingleFileTransferConnection;
|
|||
import eu.siacs.conversations.xmpp.jingle.OngoingRtpSession;
|
||||
import eu.siacs.conversations.xmpp.jingle.RtpCapability;
|
||||
import me.drakeet.support.toast.ToastCompat;
|
||||
import net.java.otr4j.session.SessionStatus;
|
||||
|
||||
public class ConversationFragment extends XmppFragment implements EditMessage.KeyboardListener, MessageAdapter.OnContactPictureLongClicked, MessageAdapter.OnContactPictureClicked {
|
||||
|
||||
|
@ -212,6 +213,12 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
|
|||
private Toast messageLoaderToast;
|
||||
private ConversationsActivity activity;
|
||||
private Menu mOptionsMenu;
|
||||
protected OnClickListener clickToVerify = new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
activity.verifyOtrSessionDialog(conversation, v);
|
||||
}
|
||||
};
|
||||
|
||||
private final SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd hh:mm (z)", Locale.US);
|
||||
|
||||
|
@ -508,6 +515,18 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
|
|||
}
|
||||
}
|
||||
};
|
||||
private OnClickListener mAnswerSmpClickListener = new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
Intent intent = new Intent(activity, VerifyOTRActivity.class);
|
||||
intent.setAction(VerifyOTRActivity.ACTION_VERIFY_CONTACT);
|
||||
intent.putExtra(EXTRA_ACCOUNT, conversation.getAccount().getJid().asBareJid().toString());
|
||||
intent.putExtra(VerifyOTRActivity.EXTRA_ACCOUNT, conversation.getAccount().getJid().asBareJid().toString());
|
||||
intent.putExtra("mode", VerifyOTRActivity.MODE_ANSWER_QUESTION);
|
||||
startActivity(intent);
|
||||
activity.overridePendingTransition(R.animator.fade_in, R.animator.fade_out);
|
||||
}
|
||||
};
|
||||
|
||||
protected OnClickListener clickToDecryptListener = new OnClickListener() {
|
||||
|
||||
|
@ -921,6 +940,9 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
|
|||
message.setUuid(UUID.randomUUID().toString());
|
||||
}
|
||||
switch (conversation.getNextEncryption()) {
|
||||
case Message.ENCRYPTION_OTR:
|
||||
sendOtrMessage(message);
|
||||
break;
|
||||
case Message.ENCRYPTION_PGP:
|
||||
sendPgpMessage(message);
|
||||
break;
|
||||
|
@ -1051,7 +1073,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
|
|||
break;
|
||||
case REQUEST_INVITE_TO_CONVERSATION:
|
||||
XmppActivity.ConferenceInvite invite = XmppActivity.ConferenceInvite.parse(data);
|
||||
if (invite != null) {
|
||||
if (invite != null && activity != null) {
|
||||
if (invite.execute(activity)) {
|
||||
activity.mToast = ToastCompat.makeText(activity, R.string.creating_conference, ToastCompat.LENGTH_LONG);
|
||||
activity.mToast.show();
|
||||
|
@ -1427,7 +1449,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
|
|||
if (ShareUtil.containsXmppUri(body)) {
|
||||
copyLink.setTitle(R.string.copy_jabber_id);
|
||||
copyLink.setVisible(true);
|
||||
} else if (Patterns.WEB_URL.matcher(body).find()) {
|
||||
} else if (Patterns.AUTOLINK_WEB_URL.matcher(body).find()) {
|
||||
copyLink.setVisible(true);
|
||||
}
|
||||
}
|
||||
|
@ -1611,6 +1633,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
|
|||
}
|
||||
switch (item.getItemId()) {
|
||||
case R.id.encryption_choice_axolotl:
|
||||
case R.id.encryption_choice_otr:
|
||||
case R.id.encryption_choice_pgp:
|
||||
case R.id.encryption_choice_none:
|
||||
handleEncryptionSelection(item);
|
||||
|
@ -1797,6 +1820,10 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
|
|||
updated = conversation.setNextEncryption(Message.ENCRYPTION_NONE);
|
||||
item.setChecked(true);
|
||||
break;
|
||||
case R.id.encryption_choice_otr:
|
||||
updated = conversation.setNextEncryption(Message.ENCRYPTION_OTR);
|
||||
item.setChecked(true);
|
||||
break;
|
||||
case R.id.encryption_choice_pgp:
|
||||
if (activity.hasPgp()) {
|
||||
if (conversation.getAccount().getPgpSignature() != null) {
|
||||
|
@ -2011,6 +2038,20 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
|
|||
activity.xmppConnectionService.getHttpConnectionManager().createNewDownloadConnection(message, true);
|
||||
}
|
||||
|
||||
private OnClickListener OTRwarning = new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
try {
|
||||
final Uri uri = Uri.parse("https://monocles.wiki/index.php?title=Monocles_Chat");
|
||||
Intent browserIntent = new Intent(Intent.ACTION_VIEW, uri);
|
||||
startActivity(browserIntent);
|
||||
} catch (Exception e) {
|
||||
ToastCompat.makeText(activity, R.string.no_application_found_to_open_link, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@SuppressLint("InflateParams")
|
||||
protected void clearHistoryDialog(final Conversation conversation) {
|
||||
final AlertDialog.Builder builder = new AlertDialog.Builder(requireActivity());
|
||||
|
@ -2118,11 +2159,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
|
|||
intent.putExtra("ALTERNATIVE_CODEC", activity.xmppConnectionService.alternativeVoiceSettings());
|
||||
break;
|
||||
case ATTACHMENT_CHOICE_LOCATION:
|
||||
if (activity.xmppConnectionService.webViewAvailable()) {
|
||||
intent = new Intent(getActivity(), ShareLocationActivity.class);
|
||||
} else {
|
||||
ToastCompat.makeText(activity, R.string.webview_not_available, ToastCompat.LENGTH_LONG).show();
|
||||
}
|
||||
intent = GeoHelper.getFetchIntent(activity);
|
||||
break;
|
||||
}
|
||||
final Context context = getActivity();
|
||||
|
@ -2662,11 +2699,15 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
|
|||
});
|
||||
builder.setPositiveButton(getString(R.string.ok),
|
||||
(dialog, which) -> {
|
||||
Intent intent = new Intent(getActivity(), ConferenceDetailsActivity.class);
|
||||
intent.setAction(ConferenceDetailsActivity.ACTION_VIEW_MUC);
|
||||
intent.putExtra("uuid", conversation.getUuid());
|
||||
startActivity(intent);
|
||||
activity.overridePendingTransition(R.animator.fade_in, R.animator.fade_out);
|
||||
try {
|
||||
Intent intent = new Intent(getActivity(), ConferenceDetailsActivity.class);
|
||||
intent.setAction(ConferenceDetailsActivity.ACTION_VIEW_MUC);
|
||||
intent.putExtra("uuid", conversation.getUuid());
|
||||
startActivity(intent);
|
||||
activity.overridePendingTransition(R.animator.fade_in, R.animator.fade_out);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
builder.create().show();
|
||||
});
|
||||
|
@ -2808,6 +2849,9 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
|
|||
}
|
||||
|
||||
private void updateSnackBar(final Conversation conversation) {
|
||||
if (conversation == null) {
|
||||
return;
|
||||
}
|
||||
final Account account = conversation.getAccount();
|
||||
final XmppConnection connection = account.getXmppConnection();
|
||||
final int mode = conversation.getMode();
|
||||
|
@ -2882,9 +2926,24 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
|
|||
}
|
||||
} else if (account.hasPendingPgpIntent(conversation)) {
|
||||
showSnackbar(R.string.openpgp_messages_found, R.string.decrypt, clickToDecryptListener);
|
||||
} else if (activity.warnUnecryptedChat()) {
|
||||
} else if (mode == Conversation.MODE_SINGLE
|
||||
&& conversation.smpRequested()) {
|
||||
showSnackbar(R.string.smp_requested, R.string.verify, this.mAnswerSmpClickListener);
|
||||
} else if (mode == Conversation.MODE_SINGLE
|
||||
&& conversation.hasValidOtrSession()
|
||||
&& (conversation.getOtrSession().getSessionStatus() == SessionStatus.ENCRYPTED)
|
||||
&& (!conversation.isOtrFingerprintVerified())) {
|
||||
showSnackbar(R.string.unknown_otr_fingerprint, R.string.verify, clickToVerify);
|
||||
} else if (connection != null
|
||||
&& connection.getFeatures().blocking()
|
||||
&& conversation.countMessages() != 0
|
||||
&& !conversation.isBlocked()
|
||||
&& conversation.isWithStranger()) {
|
||||
showSnackbar(R.string.received_message_from_stranger, R.string.block, mBlockClickListener);
|
||||
} else if (activity != null && activity.warnUnecryptedChat()) {
|
||||
if (conversation.getNextEncryption() == Message.ENCRYPTION_NONE && conversation.isSingleOrPrivateAndNonAnonymous() && ((Config.supportOmemo() && Conversation.suitableForOmemoByDefault(conversation)) ||
|
||||
(Config.supportOpenPgp() && account.isPgpDecryptionServiceConnected()))) {
|
||||
(Config.supportOpenPgp() && account.isPgpDecryptionServiceConnected()) || (
|
||||
mode == Conversation.MODE_SINGLE && Config.supportOtr()))) {
|
||||
if (ENCRYPTION_EXCEPTIONS.contains(conversation.getJid().toString()) || conversation.getJid().toString().equals(account.getJid().getDomain())) {
|
||||
hideSnackbar();
|
||||
} else {
|
||||
|
@ -3275,7 +3334,16 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke
|
|||
builder.setPositiveButton(getString(R.string.send_unencrypted), listener);
|
||||
builder.create().show();
|
||||
}
|
||||
|
||||
protected void sendOtrMessage(final Message message) {
|
||||
final ConversationsActivity activity = (ConversationsActivity) getActivity();
|
||||
final XmppConnectionService xmppService = activity.xmppConnectionService;
|
||||
activity.selectPresence(conversation,
|
||||
() -> {
|
||||
message.setCounterpart(conversation.getNextCounterpart());
|
||||
xmppService.sendMessage(message);
|
||||
messageSent();
|
||||
});
|
||||
}
|
||||
public void appendText(String text, final boolean doNotAppend) {
|
||||
if (text == null) {
|
||||
return;
|
||||
|
|
|
@ -58,6 +58,10 @@ import android.view.Menu;
|
|||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import net.java.otr4j.session.SessionStatus;
|
||||
import androidx.appcompat.widget.PopupMenu;
|
||||
|
||||
import androidx.annotation.IdRes;
|
||||
import androidx.annotation.NonNull;
|
||||
|
@ -206,13 +210,17 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
|
|||
SharedPreferences.Editor editor = FirstStart.edit();
|
||||
editor.putLong(PREF_FIRST_START, FirstStartTime);
|
||||
editor.commit();
|
||||
// restart
|
||||
Intent restartintent = getBaseContext().getPackageManager().getLaunchIntentForPackage(getBaseContext().getPackageName());
|
||||
restartintent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
restartintent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(restartintent);
|
||||
overridePendingTransition(R.animator.fade_in, R.animator.fade_out);
|
||||
System.exit(0);
|
||||
// restart if storage not accessable
|
||||
if (FileBackend.getDiskSize() > 0) {
|
||||
return;
|
||||
} else {
|
||||
Intent restartintent = getBaseContext().getPackageManager().getLaunchIntentForPackage(getBaseContext().getPackageName());
|
||||
restartintent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
restartintent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(restartintent);
|
||||
overridePendingTransition(R.animator.fade_in, R.animator.fade_out);
|
||||
System.exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
if (useInternalUpdater()) {
|
||||
|
@ -782,17 +790,17 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
|
|||
pendingViewIntent.push(intent);
|
||||
}
|
||||
} else if (intent != null && ACTION_DESTROY_MUC.equals(intent.getAction())) {
|
||||
final Bundle extras = intent.getExtras();
|
||||
if (extras != null && extras.containsKey("MUC_UUID")) {
|
||||
Log.d(Config.LOGTAG, "Get " + intent.getAction() + " intent for " + extras.getString("MUC_UUID"));
|
||||
Conversation conversation = xmppConnectionService.findConversationByUuid(extras.getString("MUC_UUID"));
|
||||
try {
|
||||
try {
|
||||
final Bundle extras = intent.getExtras();
|
||||
if (extras != null && extras.containsKey("MUC_UUID")) {
|
||||
Log.d(Config.LOGTAG, "Get " + intent.getAction() + " intent for " + extras.getString("MUC_UUID"));
|
||||
Conversation conversation = xmppConnectionService.findConversationByUuid(extras.getString("MUC_UUID"));
|
||||
ConversationsActivity.this.xmppConnectionService.clearConversationHistory(conversation);
|
||||
xmppConnectionService.destroyRoom(conversation, ConversationsActivity.this);
|
||||
endConversation(conversation);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
setIntent(createLauncherIntent(this));
|
||||
|
@ -975,7 +983,32 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void verifyOtrSessionDialog(final Conversation conversation, View view) {
|
||||
if (!conversation.hasValidOtrSession() || conversation.getOtrSession().getSessionStatus() != SessionStatus.ENCRYPTED) {
|
||||
ToastCompat.makeText(this, R.string.otr_session_not_started, Toast.LENGTH_LONG).show();
|
||||
return;
|
||||
}
|
||||
if (view == null) {
|
||||
return;
|
||||
}
|
||||
PopupMenu popup = new PopupMenu(this, view);
|
||||
popup.inflate(R.menu.verification_choices);
|
||||
popup.setOnMenuItemClickListener(menuItem -> {
|
||||
Intent intent = new Intent(ConversationsActivity.this, VerifyOTRActivity.class);
|
||||
intent.setAction(VerifyOTRActivity.ACTION_VERIFY_CONTACT);
|
||||
intent.putExtra("contact", conversation.getContact().getJid().asBareJid().toString());
|
||||
intent.putExtra(EXTRA_ACCOUNT, conversation.getAccount().getJid().asBareJid().toString());
|
||||
switch (menuItem.getItemId()) {
|
||||
case R.id.ask_question:
|
||||
intent.putExtra("mode", VerifyOTRActivity.MODE_ASK_QUESTION);
|
||||
break;
|
||||
}
|
||||
startActivity(intent);
|
||||
overridePendingTransition(R.animator.fade_in, R.animator.fade_out);
|
||||
return true;
|
||||
});
|
||||
popup.show();
|
||||
}
|
||||
@Override
|
||||
public void onConversationArchived(Conversation conversation) {
|
||||
if (performRedirectIfNecessary(conversation, false)) {
|
||||
|
|
|
@ -184,6 +184,7 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
|
|||
jid = Jid.ofEscaped(binding.accountJid.getText().toString(), getUserModeDomain(), null);
|
||||
} else {
|
||||
jid = Jid.ofEscaped(binding.accountJid.getText().toString());
|
||||
Resolver.checkDomain(jid);
|
||||
}
|
||||
} catch (final NullPointerException e) {
|
||||
if (mUsernameMode) {
|
||||
|
|
|
@ -221,17 +221,7 @@ public class ImportBackupActivity extends XmppActivity implements ServiceConnect
|
|||
|
||||
@Override
|
||||
public void onBackupRestored() {
|
||||
runOnUiThread(this::restart);
|
||||
}
|
||||
|
||||
private void restart() {
|
||||
Log.d(Config.LOGTAG, "Restarting " + getBaseContext().getPackageManager().getLaunchIntentForPackage(getBaseContext().getPackageName()));
|
||||
Intent intent = getBaseContext().getPackageManager().getLaunchIntentForPackage(getBaseContext().getPackageName());
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
startActivity(intent);
|
||||
overridePendingTransition(R.animator.fade_in, R.animator.fade_out);
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -87,6 +87,7 @@ public class SettingsActivity extends XmppActivity implements
|
|||
public static final String MAPPREVIEW_HOST = "mappreview_host";
|
||||
public static final String ALLOW_MESSAGE_CORRECTION = "allow_message_correction";
|
||||
public static final String ALLOW_MESSAGE_RETRACTION = "allow_message_retraction";
|
||||
public static final String ENABLE_OTR_ENCRYPTION = "enable_otr_encryption";
|
||||
public static final String USE_UNICOLORED_CHATBG = "unicolored_chatbg";
|
||||
public static final String EASY_DOWNLOADER = "easy_downloader";
|
||||
public static final String MIN_ANDROID_SDK21_SHOWN = "min_android_sdk21_shown";
|
||||
|
|
|
@ -2,6 +2,7 @@ package eu.siacs.conversations.ui;
|
|||
|
||||
import static eu.siacs.conversations.ui.SettingsActivity.USE_INTERNAL_UPDATER;
|
||||
|
||||
import androidx.annotation.RequiresApi;
|
||||
import android.Manifest;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.TargetApi;
|
||||
|
@ -104,6 +105,13 @@ import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
|
|||
import eu.siacs.conversations.xmpp.XmppConnection;
|
||||
import me.drakeet.support.toast.ToastCompat;
|
||||
import pl.droidsonroids.gif.GifDrawable;
|
||||
import android.util.Pair;
|
||||
import net.java.otr4j.session.SessionID;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import eu.siacs.conversations.utils.CryptoHelper;
|
||||
import static eu.siacs.conversations.ui.SettingsActivity.ENABLE_OTR_ENCRYPTION;
|
||||
|
||||
public abstract class XmppActivity extends ActionBarActivity {
|
||||
|
||||
|
@ -418,7 +426,17 @@ public abstract class XmppActivity extends ActionBarActivity {
|
|||
|
||||
public void selectPresence(final Conversation conversation, final PresenceSelector.OnPresenceSelected listener) {
|
||||
final Contact contact = conversation.getContact();
|
||||
if (contact.showInRoster() || contact.isSelf()) {
|
||||
if (conversation.hasValidOtrSession()) {
|
||||
SessionID id = conversation.getOtrSession().getSessionID();
|
||||
Jid jid;
|
||||
try {
|
||||
jid = Jid.of(id.getAccountID() + "/" + id.getUserID());
|
||||
} catch (IllegalArgumentException e) {
|
||||
jid = null;
|
||||
}
|
||||
conversation.setNextCounterpart(jid);
|
||||
listener.onPresenceSelected();
|
||||
} else if (contact.showInRoster() || contact.isSelf()) {
|
||||
final Presences presences = contact.getPresences();
|
||||
if (presences.size() == 0) {
|
||||
if (contact.isSelf()) {
|
||||
|
@ -484,7 +502,9 @@ public abstract class XmppActivity extends ActionBarActivity {
|
|||
public boolean unicoloredBG() {
|
||||
return getBooleanPreference("unicolored_chatbg", R.bool.use_unicolored_chatbg) || getPreferences().getString(SettingsActivity.THEME, getString(R.string.theme)).equals("black");
|
||||
}
|
||||
|
||||
public boolean enableOTR() {
|
||||
return getBooleanPreference(ENABLE_OTR_ENCRYPTION, R.bool.enable_otr);
|
||||
}
|
||||
public boolean showDateInQuotes() {
|
||||
return getBooleanPreference("show_date_in_quotes", R.bool.show_date_in_quotes);
|
||||
}
|
||||
|
@ -522,12 +542,22 @@ public abstract class XmppActivity extends ActionBarActivity {
|
|||
final ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
return cm != null
|
||||
&& cm.isActiveNetworkMetered()
|
||||
&& cm.getRestrictBackgroundStatus() == ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
|
||||
&& getRestrictBackgroundStatus(cm) == ConnectivityManager.RESTRICT_BACKGROUND_STATUS_ENABLED;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.N)
|
||||
private static int getRestrictBackgroundStatus(@NonNull final ConnectivityManager connectivityManager) {
|
||||
try {
|
||||
return connectivityManager.getRestrictBackgroundStatus();
|
||||
} catch (final Exception e) {
|
||||
Log.d(Config.LOGTAG, "platform bug detected. Unable to get restrict background status", e);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean usingEnterKey() {
|
||||
return getBooleanPreference("display_enter_key", R.bool.display_enter_key);
|
||||
}
|
||||
|
|
|
@ -1276,10 +1276,11 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
|||
} else if (transferable != null && transferable.getStatus() == Transferable.STATUS_OFFER_CHECK_FILESIZE) {
|
||||
displayDownloadableMessage(viewHolder, message, activity.getString(R.string.check_x_filesize, UIHelper.getFileDescriptionString(activity, message)), darkBackground);
|
||||
} else {
|
||||
/* todo why should we mark a file as deleted? --> causing strange side effects
|
||||
if (!activity.xmppConnectionService.getFileBackend().getFile(message).exists() && !message.isFileDeleted()) {
|
||||
markFileDeleted(message);
|
||||
displayInfoMessage(viewHolder, activity.getString(R.string.file_deleted), darkBackground, message);
|
||||
}
|
||||
}*/
|
||||
if (checkFileExistence(message, view, viewHolder)) {
|
||||
markFileExisting(message);
|
||||
}
|
||||
|
@ -1445,16 +1446,12 @@ public class MessageAdapter extends ArrayAdapter<Message> {
|
|||
}
|
||||
|
||||
private void showLocation(Message message) {
|
||||
if (activity.xmppConnectionService.webViewAvailable()) {
|
||||
for (Intent intent : GeoHelper.createGeoIntentsFromMessage(this.getContext(), message)) {
|
||||
if (intent.resolveActivity(getContext().getPackageManager()) != null) {
|
||||
getContext().startActivity(intent);
|
||||
activity.overridePendingTransition(R.animator.fade_in, R.animator.fade_out);
|
||||
return;
|
||||
}
|
||||
for (Intent intent : GeoHelper.createGeoIntentsFromMessage(this.getContext(), message)) {
|
||||
if (intent.resolveActivity(getContext().getPackageManager()) != null) {
|
||||
getContext().startActivity(intent);
|
||||
activity.overridePendingTransition(R.animator.fade_in, R.animator.fade_out);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
ToastCompat.makeText(activity, R.string.webview_not_available, ToastCompat.LENGTH_LONG).show();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -104,6 +104,7 @@ public class ConversationMenuConfigurator {
|
|||
return;
|
||||
}
|
||||
final MenuItem none = menu.findItem(R.id.encryption_choice_none);
|
||||
final MenuItem otr = menu.findItem(R.id.encryption_choice_otr);
|
||||
final MenuItem pgp = menu.findItem(R.id.encryption_choice_pgp);
|
||||
final MenuItem axolotl = menu.findItem(R.id.encryption_choice_axolotl);
|
||||
|
||||
|
@ -131,11 +132,18 @@ public class ConversationMenuConfigurator {
|
|||
if (conversation.getNextEncryption() != Message.ENCRYPTION_NONE) {
|
||||
menuSecure.setIcon(R.drawable.ic_lock_white_24dp);
|
||||
}
|
||||
|
||||
otr.setVisible(Config.supportOtr() && activity.enableOTR());
|
||||
if (conversation.getMode() == Conversation.MODE_MULTI) {
|
||||
otr.setVisible(false);
|
||||
}
|
||||
pgp.setVisible(Config.supportOpenPgp());
|
||||
none.setVisible(Config.supportUnencrypted() || conversation.getMode() == Conversation.MODE_MULTI);
|
||||
axolotl.setVisible(Config.supportOmemo());
|
||||
switch (conversation.getNextEncryption()) {
|
||||
case Message.ENCRYPTION_OTR:
|
||||
menuSecure.setTitle(R.string.encryption_choice_otr);
|
||||
otr.setChecked(true);
|
||||
break;
|
||||
case Message.ENCRYPTION_PGP:
|
||||
menuSecure.setTitle(R.string.encrypted_with_openpgp);
|
||||
pgp.setChecked(true);
|
||||
|
|
|
@ -287,7 +287,9 @@ public class MyLinkify {
|
|||
if (end < cs.length()) {
|
||||
// Reject strings that were probably matched only because they contain a dot followed by
|
||||
// by some known TLD (see also comment for WORD_BOUNDARY in Patterns.java)
|
||||
return !isAlphabetic(cs.charAt(end - 1)) || !isAlphabetic(cs.charAt(end));
|
||||
if (isAlphabetic(cs.charAt(end - 1)) && isAlphabetic(cs.charAt(end))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
@ -317,7 +319,7 @@ public class MyLinkify {
|
|||
|
||||
public static void addLinks(Editable body, boolean includeGeo) {
|
||||
Linkify.addLinks(body, Patterns.XMPP_PATTERN, "xmpp", XMPPURI_MATCH_FILTER, null);
|
||||
Linkify.addLinks(body, Patterns.WEB_URL, "http", null, WEBURL_TRANSFORM_FILTER);
|
||||
Linkify.addLinks(body, Patterns.AUTOLINK_WEB_URL, "http", WEBURL_MATCH_FILTER, WEBURL_TRANSFORM_FILTER);
|
||||
if (includeGeo) {
|
||||
Linkify.addLinks(body, GeoHelper.GEO_URI, "geo");
|
||||
}
|
||||
|
|
|
@ -123,7 +123,7 @@ public class ShareUtil {
|
|||
return;
|
||||
}
|
||||
}
|
||||
Matcher webUrlPatternMatcher = Patterns.WEB_URL.matcher(body);
|
||||
Matcher webUrlPatternMatcher = Patterns.AUTOLINK_WEB_URL.matcher(body);
|
||||
if (webUrlPatternMatcher.find()) {
|
||||
String url = body.substring(webUrlPatternMatcher.start(), webUrlPatternMatcher.end());
|
||||
if (activity.copyTextToClipboard(url, R.string.web_address)) {
|
||||
|
|
|
@ -47,10 +47,6 @@ public class Compatibility {
|
|||
return Build.VERSION.SDK_INT < Build.VERSION_CODES.M || (ContextCompat.checkSelfPermission(context, android.Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED && ContextCompat.checkSelfPermission(context, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED);
|
||||
}
|
||||
|
||||
public static boolean runsTwentySix() {
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
|
||||
}
|
||||
|
||||
public static boolean runsTwentyThree() {
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M;
|
||||
}
|
||||
|
@ -59,6 +55,10 @@ public class Compatibility {
|
|||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
|
||||
}
|
||||
|
||||
public static boolean runsTwentySix() {
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O;
|
||||
}
|
||||
|
||||
public static boolean runsTwentyEight() {
|
||||
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.P;
|
||||
}
|
||||
|
|
|
@ -131,7 +131,11 @@ public abstract class ConversationsFileObserver {
|
|||
}
|
||||
return;
|
||||
}
|
||||
ConversationsFileObserver.this.onEvent(event, file);
|
||||
try {
|
||||
ConversationsFileObserver.this.onEvent(event, file);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -69,7 +69,7 @@ public class ExceptionHelper {
|
|||
report.append("Device: ").append(deviceName).append('\n');
|
||||
report.append("Android SDK: ").append(sdkVersion).append(" (").append(release).append(")").append('\n');
|
||||
report.append("Version: ").append(packageInfo.versionName).append('\n');
|
||||
report.append(String.format(Locale.ROOT, "Version: %s(%d)", BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE)).append('\n');
|
||||
report.append(String.format(Locale.ROOT, "Version: %s(%d) ", BuildConfig.VERSION_NAME, BuildConfig.VERSION_CODE)).append('\n');
|
||||
report.append("Last Update: ").append(DATE_FORMAT.format(new Date(packageInfo.lastUpdateTime))).append('\n');
|
||||
Signature[] signatures = packageInfo.signatures;
|
||||
if (signatures != null && signatures.length >= 1) {
|
||||
|
|
|
@ -25,6 +25,7 @@ import eu.siacs.conversations.entities.Conversational;
|
|||
import eu.siacs.conversations.entities.Message;
|
||||
import eu.siacs.conversations.ui.SettingsActivity;
|
||||
import eu.siacs.conversations.ui.ShowLocationActivity;
|
||||
import eu.siacs.conversations.ui.ShareLocationActivity;
|
||||
|
||||
public class GeoHelper {
|
||||
|
||||
|
@ -76,6 +77,9 @@ public class GeoHelper {
|
|||
}
|
||||
return false;
|
||||
}
|
||||
public static Intent getFetchIntent(Context context) {
|
||||
return new Intent(context, ShareLocationActivity.class);
|
||||
}
|
||||
|
||||
private static GeoPoint parseGeoPoint(String body) throws IllegalArgumentException {
|
||||
Matcher matcher = GEO_URI.matcher(body);
|
||||
|
|
|
@ -46,6 +46,7 @@ import eu.siacs.conversations.Config;
|
|||
import eu.siacs.conversations.R;
|
||||
import eu.siacs.conversations.persistance.FileBackend;
|
||||
import eu.siacs.conversations.services.XmppConnectionService;
|
||||
import eu.siacs.conversations.xmpp.Jid;
|
||||
|
||||
public class Resolver {
|
||||
|
||||
|
@ -92,6 +93,9 @@ public class Resolver {
|
|||
}
|
||||
return happyEyeball(resolveNoSrvRecords(DNSName.from(hostname), port, true));
|
||||
}
|
||||
public static void checkDomain(final Jid jid) {
|
||||
DNSName.from(jid.getDomain());
|
||||
}
|
||||
|
||||
public static boolean invalidHostname(final String hostname) {
|
||||
try {
|
||||
|
|
|
@ -134,7 +134,7 @@ public class RichPreview {
|
|||
}
|
||||
|
||||
private String resolveURL(String url, String part) {
|
||||
if (Patterns.WEB_URL.matcher(part).matches() && !part.contains(" ")) {
|
||||
if (Patterns.AUTOLINK_WEB_URL.matcher(part).matches() && !part.contains(" ")) {
|
||||
return part;
|
||||
} else {
|
||||
URI base_uri = null;
|
||||
|
|
|
@ -511,9 +511,6 @@ public class UIHelper {
|
|||
}
|
||||
|
||||
public static String getFileDescriptionString(final Context context, final Message message) {
|
||||
if (message.getType() == Message.TYPE_IMAGE) {
|
||||
return context.getString(R.string.image);
|
||||
}
|
||||
final String mime = message.getMimeType();
|
||||
if (mime == null) {
|
||||
return context.getString(R.string.file);
|
||||
|
|
|
@ -8,23 +8,18 @@ import java.io.OutputStreamWriter;
|
|||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.xmpp.stanzas.AbstractStanza;
|
||||
|
||||
public class TagWriter {
|
||||
|
||||
private static final int FLUSH_DELAY = 400;
|
||||
|
||||
private OutputStreamWriter outputStream;
|
||||
private boolean finished = false;
|
||||
private LinkedBlockingQueue<AbstractStanza> writeQueue = new LinkedBlockingQueue<AbstractStanza>();
|
||||
private CountDownLatch stanzaWriterCountDownLatch = null;
|
||||
private Thread asyncStanzaWriter = new Thread() {
|
||||
|
||||
private final AtomicInteger batchStanzaCount = new AtomicInteger(0);
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
stanzaWriterCountDownLatch = new CountDownLatch(1);
|
||||
|
@ -33,21 +28,12 @@ public class TagWriter {
|
|||
break;
|
||||
}
|
||||
try {
|
||||
final AbstractStanza stanza = writeQueue.poll(FLUSH_DELAY, TimeUnit.MILLISECONDS);
|
||||
if (stanza != null) {
|
||||
batchStanzaCount.incrementAndGet();
|
||||
outputStream.write(stanza.toString());
|
||||
} else {
|
||||
final int batch = batchStanzaCount.getAndSet(0);
|
||||
if (batch > 1) {
|
||||
Log.d(Config.LOGTAG, "flushing " + batch + " stanzas");
|
||||
}
|
||||
AbstractStanza output = writeQueue.take();
|
||||
outputStream.write(output.toString());
|
||||
if (writeQueue.size() == 0) {
|
||||
outputStream.flush();
|
||||
final AbstractStanza nextStanza = writeQueue.take();
|
||||
batchStanzaCount.incrementAndGet();
|
||||
outputStream.write(nextStanza.toString());
|
||||
}
|
||||
} catch (final Exception e) {
|
||||
} catch (Exception e) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -521,7 +521,7 @@ public class XmppConnection implements Runnable {
|
|||
if (Namespace.SASL.equals(failure.getNamespace())) {
|
||||
final String text = failure.findChildContent("text");
|
||||
if (failure.hasChild("account-disabled") && text != null) {
|
||||
Matcher matcher = Patterns.WEB_URL.matcher(text);
|
||||
Matcher matcher = Patterns.AUTOLINK_WEB_URL.matcher(text);
|
||||
if (matcher.find()) {
|
||||
final HttpUrl url;
|
||||
try {
|
||||
|
@ -1050,7 +1050,7 @@ public class XmppConnection implements Runnable {
|
|||
if (url != null) {
|
||||
setAccountCreationFailed(url);
|
||||
} else if (instructions != null) {
|
||||
final Matcher matcher = Patterns.WEB_URL.matcher(instructions);
|
||||
final Matcher matcher = Patterns.AUTOLINK_WEB_URL.matcher(instructions);
|
||||
if (matcher.find()) {
|
||||
setAccountCreationFailed(instructions.substring(matcher.start(), matcher.end()));
|
||||
}
|
||||
|
|
|
@ -1199,4 +1199,5 @@
|
|||
<string name="verifying_omemo_keys_trusted_source_account">Estás a punto de verificar las claves OMEMO de tu propia cuenta. Esto es únicamente seguro si has seguido este enlace desde un origen de confianza donde solo tú puedes haber este enlace.</string>
|
||||
<string name="continue_btn">Continuar</string>
|
||||
<string name="participants">Participantes</string>
|
||||
<string name="download_failed_invalid_file">Descarga fallida: Fichero no válido</string>
|
||||
</resources>
|
||||
|
|
|
@ -1201,4 +1201,5 @@
|
|||
<string name="verifying_omemo_keys_trusted_source_account">You are about to verify the OMEMO keys of your own account. This is only secure if you followed this link from a trusted source where only you could have published this link.</string>
|
||||
<string name="continue_btn">Continue</string>
|
||||
<string name="participants">Participants</string>
|
||||
<string name="download_failed_invalid_file">Download failed: Invalid file</string>
|
||||
</resources>
|
||||
|
|
|
@ -509,6 +509,11 @@
|
|||
android:key="delete_omemo_identities"
|
||||
android:summary="@string/pref_delete_omemo_identities_summary"
|
||||
android:title="@string/pref_delete_omemo_identities" />
|
||||
<CheckBoxPreference
|
||||
android:defaultValue="@bool/enable_otr"
|
||||
android:key="enable_otr_encryption"
|
||||
android:summary="@string/pref_enable_otr_summary"
|
||||
android:title="@string/pref_enable_otr" />
|
||||
<CheckBoxPreference
|
||||
android:defaultValue="@bool/dont_trust_system_cas"
|
||||
android:key="dont_trust_system_cas"
|
||||
|
|
Loading…
Reference in a new issue