aboutsummaryrefslogtreecommitdiffstats
path: root/src/main/java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java')
-rw-r--r--src/main/java/eu/siacs/conversations/Config.java7
-rw-r--r--src/main/java/eu/siacs/conversations/OmemoActivity.java69
-rw-r--r--src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java142
-rw-r--r--src/main/java/eu/siacs/conversations/crypto/axolotl/FingerprintStatus.java42
-rw-r--r--src/main/java/eu/siacs/conversations/crypto/axolotl/SQLiteAxolotlStore.java11
-rw-r--r--src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java11
-rw-r--r--src/main/java/eu/siacs/conversations/entities/Account.java7
-rw-r--r--src/main/java/eu/siacs/conversations/entities/Contact.java4
-rw-r--r--src/main/java/eu/siacs/conversations/entities/Conversation.java5
-rw-r--r--src/main/java/eu/siacs/conversations/entities/Message.java4
-rw-r--r--src/main/java/eu/siacs/conversations/entities/MucOptions.java35
-rw-r--r--src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java14
-rw-r--r--src/main/java/eu/siacs/conversations/parser/IqParser.java9
-rw-r--r--src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java43
-rw-r--r--src/main/java/eu/siacs/conversations/persistance/FileBackend.java47
-rw-r--r--src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java2
-rw-r--r--src/main/java/eu/siacs/conversations/services/AvatarService.java5
-rw-r--r--src/main/java/eu/siacs/conversations/services/BarcodeProvider.java206
-rw-r--r--src/main/java/eu/siacs/conversations/services/ExportLogsService.java2
-rw-r--r--src/main/java/eu/siacs/conversations/services/NotificationService.java3
-rw-r--r--src/main/java/eu/siacs/conversations/services/XmppConnectionService.java260
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java2
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java31
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ConversationActivity.java13
-rw-r--r--src/main/java/eu/siacs/conversations/ui/ConversationFragment.java9
-rw-r--r--src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java86
-rw-r--r--src/main/java/eu/siacs/conversations/ui/SettingsActivity.java95
-rw-r--r--src/main/java/eu/siacs/conversations/ui/SettingsFragment.java12
-rw-r--r--src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java28
-rw-r--r--src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java84
-rw-r--r--src/main/java/eu/siacs/conversations/ui/XmppActivity.java35
-rw-r--r--src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java29
-rw-r--r--src/main/java/eu/siacs/conversations/utils/CryptoHelper.java6
-rw-r--r--src/main/java/eu/siacs/conversations/utils/TLSSocketFactory.java69
-rw-r--r--src/main/java/eu/siacs/conversations/utils/XmppUri.java34
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java161
-rw-r--r--src/main/java/eu/siacs/conversations/xmpp/jid/Jid.java14
37 files changed, 1218 insertions, 418 deletions
diff --git a/src/main/java/eu/siacs/conversations/Config.java b/src/main/java/eu/siacs/conversations/Config.java
index 33069439..6d69d36e 100644
--- a/src/main/java/eu/siacs/conversations/Config.java
+++ b/src/main/java/eu/siacs/conversations/Config.java
@@ -78,6 +78,10 @@ public final class Config {
public static final int MAX_DISPLAY_MESSAGE_CHARS = 4096;
+ public static final long MILLISECONDS_IN_DAY = 24 * 60 * 60 * 1000;
+
+ public static final long OMEMO_AUTO_EXPIRY = 7 * MILLISECONDS_IN_DAY;
+
public static final boolean DISABLE_PROXY_LOOKUP = false; //useful to debug ibb
public static final boolean DISABLE_HTTP_UPLOAD = false;
public static final boolean DISABLE_STRING_PREP = false; // setting to true might increase startup performance
@@ -93,11 +97,12 @@ public final class Config {
public static final boolean X509_VERIFICATION = false; //use x509 certificates to verify OMEMO keys
+ 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 PARSE_REAL_JID_FROM_MUC_MAM = false; //dangerous if server doesn’t filter
- public static final long MILLISECONDS_IN_DAY = 24 * 60 * 60 * 1000;
public static final long MAM_MAX_CATCHUP = MILLISECONDS_IN_DAY / 2;
public static final int MAM_MAX_MESSAGES = 500;
diff --git a/src/main/java/eu/siacs/conversations/OmemoActivity.java b/src/main/java/eu/siacs/conversations/OmemoActivity.java
index c4177cd2..c0c7b298 100644
--- a/src/main/java/eu/siacs/conversations/OmemoActivity.java
+++ b/src/main/java/eu/siacs/conversations/OmemoActivity.java
@@ -2,6 +2,7 @@ package eu.siacs.conversations;
import android.app.AlertDialog;
import android.content.DialogInterface;
+import android.content.Intent;
import android.os.Bundle;
import android.view.ContextMenu;
import android.view.MenuItem;
@@ -12,13 +13,20 @@ import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
+import com.google.zxing.integration.android.IntentIntegrator;
+import com.google.zxing.integration.android.IntentResult;
+
import java.security.cert.X509Certificate;
+import java.util.Arrays;
import eu.siacs.conversations.crypto.axolotl.FingerprintStatus;
+import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.ui.TrustKeysActivity;
import eu.siacs.conversations.ui.XmppActivity;
import eu.siacs.conversations.ui.widget.Switch;
import eu.siacs.conversations.utils.CryptoHelper;
+import eu.siacs.conversations.utils.XmppUri;
public abstract class OmemoActivity extends XmppActivity {
@@ -26,13 +34,32 @@ public abstract class OmemoActivity extends XmppActivity {
private Account mSelectedAccount;
private String mSelectedFingerprint;
+ protected XmppUri mPendingFingerprintVerificationUri = null;
+
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu,v,menuInfo);
Object account = v.getTag(R.id.TAG_ACCOUNT);
Object fingerprint = v.getTag(R.id.TAG_FINGERPRINT);
- if (account != null && fingerprint != null && account instanceof Account && fingerprint instanceof String) {
+ Object fingerprintStatus = v.getTag(R.id.TAG_FINGERPRINT_STATUS);;
+ if (account != null
+ && fingerprint != null
+ && account instanceof Account
+ && fingerprintStatus != null
+ && fingerprint instanceof String
+ && fingerprintStatus instanceof FingerprintStatus) {
getMenuInflater().inflate(R.menu.omemo_key_context, menu);
+ MenuItem purgeItem = menu.findItem(R.id.purge_omemo_key);
+ MenuItem verifyScan = menu.findItem(R.id.verify_scan);
+ if (this instanceof TrustKeysActivity) {
+ purgeItem.setVisible(false);
+ verifyScan.setVisible(false);
+ } else {
+ FingerprintStatus status = (FingerprintStatus) fingerprintStatus;
+ if (!status.isActive() || status.isVerified()) {
+ verifyScan.setVisible(false);
+ }
+ }
this.mSelectedAccount = (Account) account;
this.mSelectedFingerprint = (String) fingerprint;
}
@@ -47,10 +74,29 @@ public abstract class OmemoActivity extends XmppActivity {
case R.id.copy_omemo_key:
copyOmemoFingerprint(mSelectedFingerprint);
break;
+ case R.id.verify_scan:
+ new IntentIntegrator(this).initiateScan(Arrays.asList("AZTEC","QR_CODE"));
+ break;
}
return true;
}
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent intent) {
+ IntentResult scanResult = IntentIntegrator.parseActivityResult(requestCode, resultCode, intent);
+ if (scanResult != null && scanResult.getFormatName() != null) {
+ String data = scanResult.getContents();
+ XmppUri uri = new XmppUri(data);
+ if (xmppConnectionServiceBound) {
+ processFingerprintVerification(uri);
+ } else {
+ this.mPendingFingerprintVerificationUri =uri;
+ }
+ }
+ }
+
+ protected abstract void processFingerprintVerification(XmppUri uri);
+
protected void copyOmemoFingerprint(String fingerprint) {
if (copyTextToClipboard(CryptoHelper.prettifyFingerprint(fingerprint.substring(2)), R.string.omemo_fingerprint)) {
Toast.makeText(
@@ -60,9 +106,17 @@ public abstract class OmemoActivity extends XmppActivity {
}
}
- protected boolean addFingerprintRow(LinearLayout keys, final Account account, final String fingerprint, boolean highlight) {
- final FingerprintStatus status = account.getAxolotlService().getFingerprintTrust(fingerprint);
- return status != null && addFingerprintRowWithListeners(keys, account, fingerprint, highlight, status, true, true, new CompoundButton.OnCheckedChangeListener() {
+ protected void addFingerprintRow(LinearLayout keys, final XmppAxolotlSession session, boolean highlight) {
+ final Account account = session.getAccount();
+ final String fingerprint = session.getFingerprint();
+ addFingerprintRowWithListeners(keys,
+ session.getAccount(),
+ session.getFingerprint(),
+ highlight,
+ session.getTrust(),
+ true,
+ true,
+ new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
account.getAxolotlService().setFingerprintTrust(fingerprint, FingerprintStatus.createActive(isChecked));
@@ -70,7 +124,7 @@ public abstract class OmemoActivity extends XmppActivity {
});
}
- protected boolean addFingerprintRowWithListeners(LinearLayout keys, final Account account,
+ protected void addFingerprintRowWithListeners(LinearLayout keys, final Account account,
final String fingerprint,
boolean highlight,
FingerprintStatus status,
@@ -78,9 +132,6 @@ public abstract class OmemoActivity extends XmppActivity {
boolean undecidedNeedEnablement,
CompoundButton.OnCheckedChangeListener
onCheckedChangeListener) {
- if (status.isCompromised()) {
- return false;
- }
View view = getLayoutInflater().inflate(R.layout.contact_key, keys, false);
TextView key = (TextView) view.findViewById(R.id.key);
TextView keyType = (TextView) view.findViewById(R.id.key_type);
@@ -100,6 +151,7 @@ public abstract class OmemoActivity extends XmppActivity {
registerForContextMenu(view);
view.setTag(R.id.TAG_ACCOUNT,account);
view.setTag(R.id.TAG_FINGERPRINT,fingerprint);
+ view.setTag(R.id.TAG_FINGERPRINT_STATUS,status);
boolean x509 = Config.X509_VERIFICATION && status.getTrust() == FingerprintStatus.Trust.VERIFIED_X509;
final View.OnClickListener toast;
trustToggle.setChecked(status.isTrusted(), false);
@@ -184,7 +236,6 @@ public abstract class OmemoActivity extends XmppActivity {
key.setText(CryptoHelper.prettifyFingerprint(fingerprint.substring(2)));
keys.addView(view);
- return true;
}
public void showPurgeKeyDialog(final Account account, final String fingerprint) {
diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java
index 702d4ada..09beb22c 100644
--- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java
+++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java
@@ -25,13 +25,17 @@ import java.security.PrivateKey;
import java.security.Security;
import java.security.Signature;
import java.security.cert.X509Certificate;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.entities.Account;
@@ -73,6 +77,8 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
private int numPublishTriesOnEmptyPep = 0;
private boolean pepBroken = false;
+ private AtomicBoolean ownPushPending = new AtomicBoolean(false);
+
@Override
public void onAdvancedStreamFeaturesAvailable(Account account) {
if (Config.supportOmemo()
@@ -88,7 +94,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
for(Jid jid : jids) {
if (deviceIds.get(jid) != null) {
for (Integer foreignId : this.deviceIds.get(jid)) {
- AxolotlAddress address = new AxolotlAddress(jid.toString(), foreignId);
+ AxolotlAddress address = new AxolotlAddress(jid.toPreppedString(), foreignId);
if (fetchStatusMap.getAll(address).containsValue(FetchStatus.ERROR)) {
return true;
}
@@ -106,6 +112,15 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
axolotlStore.preVerifyFingerprint(account, account.getJid().toBareJid().toPreppedString(), fingerprint);
}
+ public boolean hasVerifiedKeys(String name) {
+ for(XmppAxolotlSession session : this.sessions.getAll(new AxolotlAddress(name,0)).values()) {
+ if (session.getTrust().isVerified()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private static class AxolotlAddressMap<T> {
protected Map<String, Map<Integer, T>> map;
protected final Object MAP_LOCK = new Object();
@@ -172,7 +187,6 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
private void putDevicesForJid(String bareJid, List<Integer> deviceIds, SQLiteAxolotlStore store) {
for (Integer deviceId : deviceIds) {
AxolotlAddress axolotlAddress = new AxolotlAddress(bareJid, deviceId);
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building session for remote address: " + axolotlAddress.toString());
IdentityKey identityKey = store.loadSession(axolotlAddress).getSessionState().getRemoteIdentityKey();
if(Config.X509_VERIFICATION) {
X509Certificate certificate = store.getFingerprintCertificate(identityKey.getFingerprint().replaceAll("\\s", ""));
@@ -221,6 +235,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
SUCCESS,
SUCCESS_VERIFIED,
TIMEOUT,
+ SUCCESS_TRUSTED,
ERROR
}
@@ -297,16 +312,20 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
return new AxolotlAddress(jid.toPreppedString(), 0);
}
- public Set<XmppAxolotlSession> findOwnSessions() {
+ public Collection<XmppAxolotlSession> findOwnSessions() {
AxolotlAddress ownAddress = getAddressForJid(account.getJid().toBareJid());
- return new HashSet<>(this.sessions.getAll(ownAddress).values());
+ ArrayList<XmppAxolotlSession> s = new ArrayList<>(this.sessions.getAll(ownAddress).values());
+ Collections.sort(s);
+ return s;
}
- private Set<XmppAxolotlSession> findSessionsForContact(Contact contact) {
+ public Collection<XmppAxolotlSession> findSessionsForContact(Contact contact) {
AxolotlAddress contactAddress = getAddressForJid(contact.getJid());
- return new HashSet<>(this.sessions.getAll(contactAddress).values());
+ ArrayList<XmppAxolotlSession> s = new ArrayList<>(this.sessions.getAll(contactAddress).values());
+ Collections.sort(s);
+ return s;
}
private Set<XmppAxolotlSession> findSessionsForConversation(Conversation conversation) {
@@ -317,22 +336,6 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
return sessions;
}
- public Set<String> getFingerprintsForOwnSessions() {
- Set<String> fingerprints = new HashSet<>();
- for (XmppAxolotlSession session : findOwnSessions()) {
- fingerprints.add(session.getFingerprint());
- }
- return fingerprints;
- }
-
- public Set<String> getFingerprintsForContact(final Contact contact) {
- Set<String> fingerprints = new HashSet<>();
- for (XmppAxolotlSession session : findSessionsForContact(contact)) {
- fingerprints.add(session.getFingerprint());
- }
- return fingerprints;
- }
-
private boolean hasAny(Jid jid) {
return sessions.hasAny(getAddressForJid(jid));
}
@@ -366,23 +369,14 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
}
public void registerDevices(final Jid jid, @NonNull final Set<Integer> deviceIds) {
- if (jid.toBareJid().equals(account.getJid().toBareJid())) {
- if (!deviceIds.isEmpty()) {
- Log.d(Config.LOGTAG, getLogprefix(account) + "Received non-empty own device list. Resetting publish attempts and pepBroken status.");
- pepBroken = false;
- numPublishTriesOnEmptyPep = 0;
- }
- if (deviceIds.contains(getOwnDeviceId())) {
- deviceIds.remove(getOwnDeviceId());
- } else {
- publishOwnDeviceId(deviceIds);
- }
- for (Integer deviceId : deviceIds) {
- AxolotlAddress ownDeviceAddress = new AxolotlAddress(jid.toBareJid().toPreppedString(), deviceId);
- if (sessions.get(ownDeviceAddress) == null) {
- buildSessionFromPEP(ownDeviceAddress);
- }
- }
+ boolean me = jid.toBareJid().equals(account.getJid().toBareJid());
+ if (me && ownPushPending.getAndSet(false)) {
+ Log.d(Config.LOGTAG,account.getJid().toBareJid()+": ignoring own device update because of pending push");
+ return;
+ }
+ boolean needsPublishing = me && !deviceIds.contains(getOwnDeviceId());
+ if (me) {
+ deviceIds.remove(getOwnDeviceId());
}
Set<Integer> expiredDevices = new HashSet<>(axolotlStore.getSubDeviceSessions(jid.toBareJid().toPreppedString()));
expiredDevices.removeAll(deviceIds);
@@ -401,11 +395,27 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
XmppAxolotlSession session = sessions.get(address);
if (session != null && session.getFingerprint() != null) {
if (!session.getTrust().isActive()) {
+ Log.d(Config.LOGTAG,"reactivating device with fingprint "+session.getFingerprint());
session.setTrust(session.getTrust().toActive());
}
}
}
+ if (me) {
+ if (Config.OMEMO_AUTO_EXPIRY != 0) {
+ needsPublishing |= deviceIds.removeAll(getExpiredDevices());
+ }
+ for (Integer deviceId : deviceIds) {
+ AxolotlAddress ownDeviceAddress = new AxolotlAddress(jid.toBareJid().toPreppedString(), deviceId);
+ if (sessions.get(ownDeviceAddress) == null) {
+ buildSessionFromPEP(ownDeviceAddress);
+ }
+ }
+ if (needsPublishing) {
+ publishOwnDeviceId(deviceIds);
+ }
+ }
this.deviceIds.put(jid, deviceIds);
+ mXmppConnectionService.updateConversationUi(); //update the lock icon
mXmppConnectionService.keyStatusUpdated(null);
}
@@ -418,12 +428,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
deviceIds.add(getOwnDeviceId());
IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(deviceIds);
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Wiping all other devices from Pep:" + publish);
- mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() {
- @Override
- public void onIqPacketReceived(Account account, IqPacket packet) {
- // TODO: implement this!
- }
- });
+ mXmppConnectionService.sendIqPacket(account, publish, null);
}
public void purgeKey(final String fingerprint) {
@@ -444,14 +449,30 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
} else {
Element item = mXmppConnectionService.getIqParser().getItem(packet);
Set<Integer> deviceIds = mXmppConnectionService.getIqParser().deviceIds(item);
- if (!deviceIds.contains(getOwnDeviceId())) {
- publishOwnDeviceId(deviceIds);
- }
+ registerDevices(account.getJid().toBareJid(),deviceIds);
}
}
});
}
+ private Set<Integer> getExpiredDevices() {
+ Set<Integer> devices = new HashSet<>();
+ for(XmppAxolotlSession session : findOwnSessions()) {
+ if (session.getTrust().isActive()) {
+ long diff = System.currentTimeMillis() - session.getTrust().getLastActivation();
+ if (diff > Config.OMEMO_AUTO_EXPIRY) {
+ long lastMessageDiff = System.currentTimeMillis() - mXmppConnectionService.databaseBackend.getLastTimeFingerprintUsed(account,session.getFingerprint());
+ if (lastMessageDiff > Config.OMEMO_AUTO_EXPIRY) {
+ devices.add(session.getRemoteAddress().getDeviceId());
+ session.setTrust(session.getTrust().toInactive());
+ Log.d(Config.LOGTAG, "added own device " + session.getFingerprint() + " to list of expired devices. Last message received "+(lastMessageDiff/1000)+"s ago");
+ }
+ }
+ }
+ }
+ return devices;
+ }
+
public void publishOwnDeviceId(Set<Integer> deviceIds) {
Set<Integer> deviceIdsCopy = new HashSet<>(deviceIds);
if (!deviceIdsCopy.contains(getOwnDeviceId())) {
@@ -470,9 +491,11 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
}
deviceIdsCopy.add(getOwnDeviceId());
IqPacket publish = mXmppConnectionService.getIqGenerator().publishDeviceIds(deviceIdsCopy);
+ ownPushPending.set(true);
mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() {
@Override
public void onIqPacketReceived(Account account, IqPacket packet) {
+ ownPushPending.set(false);
if (packet.getType() == IqPacket.TYPE.ERROR) {
pepBroken = true;
Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing own device id" + packet.findChild("error"));
@@ -767,6 +790,8 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
report = FetchStatus.SUCCESS;
} else if (own.containsValue(FetchStatus.SUCCESS_VERIFIED) || remote.containsValue(FetchStatus.SUCCESS_VERIFIED)) {
report = FetchStatus.SUCCESS_VERIFIED;
+ } else if (own.containsValue(FetchStatus.SUCCESS_TRUSTED) || remote.containsValue(FetchStatus.SUCCESS_TRUSTED)) {
+ report = FetchStatus.SUCCESS_TRUSTED;
} else if (own.containsValue(FetchStatus.ERROR) || remote.containsValue(FetchStatus.ERROR)) {
report = FetchStatus.ERROR;
}
@@ -824,8 +849,15 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
verifySessionWithPEP(session);
} else {
FingerprintStatus status = getFingerprintTrust(bundle.getIdentityKey().getFingerprint().replaceAll("\\s",""));
- boolean verified = status != null && status.isVerified();
- fetchStatusMap.put(address, verified ? FetchStatus.SUCCESS_VERIFIED : FetchStatus.SUCCESS);
+ FetchStatus fetchStatus;
+ if (status != null && status.isVerified()) {
+ fetchStatus = FetchStatus.SUCCESS_VERIFIED;
+ } else if (status != null && status.isTrusted()) {
+ fetchStatus = FetchStatus.SUCCESS_TRUSTED;
+ } else {
+ fetchStatus = FetchStatus.SUCCESS;
+ }
+ fetchStatusMap.put(address, fetchStatus);
finishBuildingSessionsFromPEP(address);
}
} catch (UntrustedIdentityException | InvalidKeyException e) {
@@ -852,7 +884,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Finding devices without session for " + jid);
if (deviceIds.get(jid) != null) {
for (Integer foreignId : this.deviceIds.get(jid)) {
- AxolotlAddress address = new AxolotlAddress(jid.toString(), foreignId);
+ AxolotlAddress address = new AxolotlAddress(jid.toPreppedString(), foreignId);
if (sessions.get(address) == null) {
IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey();
if (identityKey != null) {
@@ -954,18 +986,14 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
account.getJid().toBareJid(), getOwnDeviceId());
Set<XmppAxolotlSession> remoteSessions = findSessionsForConversation(conversation);
- Set<XmppAxolotlSession> ownSessions = findOwnSessions();
+ Collection<XmppAxolotlSession> ownSessions = findOwnSessions();
if (remoteSessions.isEmpty()) {
return null;
}
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building axolotl foreign keyElements...");
for (XmppAxolotlSession session : remoteSessions) {
- Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account) + session.getRemoteAddress().toString());
axolotlMessage.addDevice(session);
}
- Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Building axolotl own keyElements...");
for (XmppAxolotlSession session : ownSessions) {
- Log.v(Config.LOGTAG, AxolotlService.getLogprefix(account) + session.getRemoteAddress().toString());
axolotlMessage.addDevice(session);
}
@@ -1040,7 +1068,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded {
}
private XmppAxolotlSession getReceivingSession(XmppAxolotlMessage message) {
- AxolotlAddress senderAddress = new AxolotlAddress(message.getFrom().toString(),
+ AxolotlAddress senderAddress = new AxolotlAddress(message.getFrom().toPreppedString(),
message.getSenderDeviceId());
XmppAxolotlSession session = sessions.get(senderAddress);
if (session == null) {
diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/FingerprintStatus.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/FingerprintStatus.java
index b594b5de..31b2264b 100644
--- a/src/main/java/eu/siacs/conversations/crypto/axolotl/FingerprintStatus.java
+++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/FingerprintStatus.java
@@ -3,10 +3,13 @@ package eu.siacs.conversations.crypto.axolotl;
import android.content.ContentValues;
import android.database.Cursor;
-public class FingerprintStatus {
+public class FingerprintStatus implements Comparable<FingerprintStatus> {
+
+ private static final long DO_NOT_OVERWRITE = -1;
private Trust trust = Trust.UNTRUSTED;
private boolean active = false;
+ private long lastActivation = DO_NOT_OVERWRITE;
@Override
public boolean equals(Object o) {
@@ -34,6 +37,9 @@ public class FingerprintStatus {
final ContentValues contentValues = new ContentValues();
contentValues.put(SQLiteAxolotlStore.TRUST,trust.toString());
contentValues.put(SQLiteAxolotlStore.ACTIVE,active ? 1 : 0);
+ if (lastActivation != DO_NOT_OVERWRITE) {
+ contentValues.put(SQLiteAxolotlStore.LAST_ACTIVATION,lastActivation);
+ }
return contentValues;
}
@@ -45,6 +51,7 @@ public class FingerprintStatus {
status.trust = Trust.UNTRUSTED;
}
status.active = cursor.getInt(cursor.getColumnIndex(SQLiteAxolotlStore.ACTIVE)) > 0;
+ status.lastActivation = cursor.getLong(cursor.getColumnIndex(SQLiteAxolotlStore.LAST_ACTIVATION));
return status;
}
@@ -52,6 +59,15 @@ public class FingerprintStatus {
final FingerprintStatus status = new FingerprintStatus();
status.trust = Trust.UNDECIDED;
status.active = true;
+ status.lastActivation = System.currentTimeMillis();
+ return status;
+ }
+
+ public static FingerprintStatus createActiveTrusted() {
+ final FingerprintStatus status = new FingerprintStatus();
+ status.trust = Trust.TRUSTED;
+ status.active = true;
+ status.lastActivation = System.currentTimeMillis();
return status;
}
@@ -92,6 +108,9 @@ public class FingerprintStatus {
public FingerprintStatus toActive() {
FingerprintStatus status = new FingerprintStatus();
status.trust = trust;
+ if (!status.active) {
+ status.lastActivation = System.currentTimeMillis();
+ }
status.active = true;
return status;
}
@@ -128,6 +147,27 @@ public class FingerprintStatus {
return status;
}
+ @Override
+ public int compareTo(FingerprintStatus o) {
+ if (active == o.active) {
+ if (lastActivation > o.lastActivation) {
+ return -1;
+ } else if (lastActivation < o.lastActivation) {
+ return 1;
+ } else {
+ return 0;
+ }
+ } else if (active){
+ return -1;
+ } else {
+ return 1;
+ }
+ }
+
+ public long getLastActivation() {
+ return lastActivation;
+ }
+
public enum Trust {
COMPROMISED,
UNDECIDED,
diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/SQLiteAxolotlStore.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/SQLiteAxolotlStore.java
index a3647be7..13858b74 100644
--- a/src/main/java/eu/siacs/conversations/crypto/axolotl/SQLiteAxolotlStore.java
+++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/SQLiteAxolotlStore.java
@@ -21,7 +21,10 @@ import java.util.Set;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.services.XmppConnectionService;
+import eu.siacs.conversations.xmpp.jid.InvalidJidException;
+import eu.siacs.conversations.xmpp.jid.Jid;
public class SQLiteAxolotlStore implements AxolotlStore {
@@ -38,6 +41,7 @@ public class SQLiteAxolotlStore implements AxolotlStore {
public static final String TRUSTED = "trusted"; //no longer used
public static final String TRUST = "trust";
public static final String ACTIVE = "active";
+ public static final String LAST_ACTIVATION = "last_activation";
public static final String OWN = "ownkey";
public static final String CERTIFICATE = "certificate";
@@ -190,7 +194,12 @@ public class SQLiteAxolotlStore implements AxolotlStore {
String fingerprint = identityKey.getFingerprint().replaceAll("\\s", "");
FingerprintStatus status = getFingerprintStatus(fingerprint);
if (status == null) {
- status = FingerprintStatus.createActiveUndecided(); //default for new keys
+ if (mXmppConnectionService.blindTrustBeforeVerification() && !account.getAxolotlService().hasVerifiedKeys(name)) {
+ Log.d(Config.LOGTAG,account.getJid().toBareJid()+": blindly trusted "+fingerprint+" of "+name);
+ status = FingerprintStatus.createActiveTrusted();
+ } else {
+ status = FingerprintStatus.createActiveUndecided();
+ }
} else {
status = status.toActive();
}
diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java
index 572d62c8..725757a3 100644
--- a/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java
+++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/XmppAxolotlSession.java
@@ -22,7 +22,7 @@ import org.whispersystems.libaxolotl.protocol.WhisperMessage;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.entities.Account;
-public class XmppAxolotlSession {
+public class XmppAxolotlSession implements Comparable<XmppAxolotlSession> {
private final SessionCipher cipher;
private final SQLiteAxolotlStore sqLiteAxolotlStore;
private final AxolotlAddress remoteAddress;
@@ -132,4 +132,13 @@ public class XmppAxolotlSession {
return null;
}
}
+
+ public Account getAccount() {
+ return account;
+ }
+
+ @Override
+ public int compareTo(XmppAxolotlSession o) {
+ return getTrust().compareTo(o.getTrust());
+ }
}
diff --git a/src/main/java/eu/siacs/conversations/entities/Account.java b/src/main/java/eu/siacs/conversations/entities/Account.java
index 77828b03..bb89cf17 100644
--- a/src/main/java/eu/siacs/conversations/entities/Account.java
+++ b/src/main/java/eu/siacs/conversations/entities/Account.java
@@ -19,6 +19,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
+import java.util.Locale;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
@@ -340,6 +341,10 @@ public class Account extends AbstractEntity {
}
}
+ public State getTrueStatus() {
+ return this.status;
+ }
+
public void setStatus(final State status) {
this.status = status;
}
@@ -489,7 +494,7 @@ public class Account extends AbstractEntity {
if (publicKey == null || !(publicKey instanceof DSAPublicKey)) {
return null;
}
- this.otrFingerprint = new OtrCryptoEngineImpl().getFingerprint(publicKey);
+ this.otrFingerprint = new OtrCryptoEngineImpl().getFingerprint(publicKey).toLowerCase(Locale.US);
return this.otrFingerprint;
} catch (final OtrCryptoException ignored) {
return null;
diff --git a/src/main/java/eu/siacs/conversations/entities/Contact.java b/src/main/java/eu/siacs/conversations/entities/Contact.java
index 55f5443a..b7307a8b 100644
--- a/src/main/java/eu/siacs/conversations/entities/Contact.java
+++ b/src/main/java/eu/siacs/conversations/entities/Contact.java
@@ -121,7 +121,7 @@ public class Contact implements ListItem, Blockable {
} else if (this.presenceName != null && mutualPresenceSubscription()) {
return this.presenceName;
} else if (jid.hasLocalpart()) {
- return jid.getLocalpart();
+ return jid.getUnescapedLocalpart();
} else {
return jid.getDomainpart();
}
@@ -301,7 +301,7 @@ public class Contact implements ListItem, Blockable {
for (int i = 0; i < prints.length(); ++i) {
final String print = prints.isNull(i) ? null : prints.getString(i);
if (print != null && !print.isEmpty()) {
- fingerprints.add(prints.getString(i));
+ fingerprints.add(prints.getString(i).toLowerCase(Locale.US));
}
}
}
diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java
index 245ff288..ced48913 100644
--- a/src/main/java/eu/siacs/conversations/entities/Conversation.java
+++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java
@@ -20,6 +20,7 @@ import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
+import java.util.Locale;
import eu.siacs.conversations.Config;
import eu.siacs.conversations.crypto.PgpDecryptionService;
@@ -463,7 +464,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
if (generatedName != null) {
return generatedName;
} else {
- return getJid().getLocalpart();
+ return getJid().getUnescapedLocalpart();
}
}
} else {
@@ -627,7 +628,7 @@ public class Conversation extends AbstractEntity implements Blockable, Comparabl
return null;
}
DSAPublicKey remotePubKey = (DSAPublicKey) getOtrSession().getRemotePublicKey();
- this.otrFingerprint = getAccount().getOtrService().getFingerprint(remotePubKey);
+ this.otrFingerprint = getAccount().getOtrService().getFingerprint(remotePubKey).toLowerCase(Locale.US);
} catch (final OtrCryptoException | UnsupportedOperationException ignored) {
return null;
}
diff --git a/src/main/java/eu/siacs/conversations/entities/Message.java b/src/main/java/eu/siacs/conversations/entities/Message.java
index e9f16949..20e4c5a5 100644
--- a/src/main/java/eu/siacs/conversations/entities/Message.java
+++ b/src/main/java/eu/siacs/conversations/entities/Message.java
@@ -493,7 +493,7 @@ public class Message extends AbstractEntity {
!this.getBody().startsWith(ME_COMMAND) &&
!this.bodyIsHeart() &&
!message.bodyIsHeart() &&
- this.isTrusted() == message.isTrusted()
+ ((this.axolotlFingerprint == null && message.axolotlFingerprint == null) || this.axolotlFingerprint.equals(message.getFingerprint()))
);
}
@@ -813,7 +813,7 @@ public class Message extends AbstractEntity {
public boolean isTrusted() {
FingerprintStatus s = conversation.getAccount().getAxolotlService().getFingerprintTrust(axolotlFingerprint);
- return s != null && s.isTrustedAndActive();
+ return s != null && s.isTrusted();
}
private int getPreviousEncryption() {
diff --git a/src/main/java/eu/siacs/conversations/entities/MucOptions.java b/src/main/java/eu/siacs/conversations/entities/MucOptions.java
index 8e67aa41..bbc3e370 100644
--- a/src/main/java/eu/siacs/conversations/entities/MucOptions.java
+++ b/src/main/java/eu/siacs/conversations/entities/MucOptions.java
@@ -3,7 +3,6 @@ package eu.siacs.conversations.entities;
import android.annotation.SuppressLint;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -395,10 +394,20 @@ public class MucOptions {
if (user != null) {
synchronized (users) {
users.remove(user);
- if (membersOnly() &&
- nonanonymous() &&
- user.affiliation.ranks(Affiliation.MEMBER) &&
- user.realJid != null) {
+ boolean realJidInMuc = false;
+ for (User u : users) {
+ if (user.realJid != null && user.realJid.equals(u.realJid)) {
+ realJidInMuc = true;
+ break;
+ }
+ }
+ boolean self = user.realJid != null && user.realJid.equals(account.getJid().toBareJid());
+ if (membersOnly()
+ && nonanonymous()
+ && user.affiliation.ranks(Affiliation.MEMBER)
+ && user.realJid != null
+ && !realJidInMuc
+ && !self) {
user.role = Role.NONE;
user.avatar = null;
user.fullJid = null;
@@ -508,8 +517,20 @@ public class MucOptions {
}
public List<User> getUsers(int max) {
- ArrayList<User> users = getUsers();
- return users.subList(0, Math.min(max, users.size()));
+ ArrayList<User> subset = new ArrayList<>();
+ HashSet<Jid> jids = new HashSet<>();
+ jids.add(account.getJid().toBareJid());
+ synchronized (users) {
+ for(User user : users) {
+ if (user.getRealJid() == null || jids.add(user.getRealJid())) {
+ subset.add(user);
+ }
+ if (subset.size() >= max) {
+ break;
+ }
+ }
+ }
+ return subset;
}
public int getUserCount() {
diff --git a/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java b/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java
index 1a417f32..18c60bff 100644
--- a/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java
+++ b/src/main/java/eu/siacs/conversations/http/HttpConnectionManager.java
@@ -24,6 +24,7 @@ import eu.siacs.conversations.services.AbstractConnectionManager;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.utils.CryptoHelper;
import eu.siacs.conversations.utils.SSLSocketHelper;
+import eu.siacs.conversations.utils.TLSSocketFactory;
public class HttpConnectionManager extends AbstractConnectionManager {
@@ -77,18 +78,7 @@ public class HttpConnectionManager extends AbstractConnectionManager {
new StrictHostnameVerifier());
}
try {
- final SSLContext sc = SSLSocketHelper.getSSLContext();
- sc.init(null, new X509TrustManager[]{trustManager},
- mXmppConnectionService.getRNG());
-
- final SSLSocketFactory sf = sc.getSocketFactory();
- final String[] cipherSuites = CryptoHelper.getOrderedCipherSuites(
- sf.getSupportedCipherSuites());
- if (cipherSuites.length > 0) {
- sc.getDefaultSSLParameters().setCipherSuites(cipherSuites);
-
- }
-
+ final SSLSocketFactory sf = new TLSSocketFactory(new X509TrustManager[]{trustManager}, mXmppConnectionService.getRNG());
connection.setSSLSocketFactory(sf);
connection.setHostnameVerifier(hostnameVerifier);
} catch (final KeyManagementException | NoSuchAlgorithmException ignored) {
diff --git a/src/main/java/eu/siacs/conversations/parser/IqParser.java b/src/main/java/eu/siacs/conversations/parser/IqParser.java
index 49b0db21..40eec0d2 100644
--- a/src/main/java/eu/siacs/conversations/parser/IqParser.java
+++ b/src/main/java/eu/siacs/conversations/parser/IqParser.java
@@ -26,6 +26,7 @@ import eu.siacs.conversations.Config;
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact;
+import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.utils.Xmlns;
import eu.siacs.conversations.xml.Element;
@@ -319,6 +320,14 @@ public class IqParser extends AbstractParser implements OnIqPacketReceived {
}
}
account.getBlocklist().addAll(jids);
+ if (packet.getType() == IqPacket.TYPE.SET) {
+ for(Jid jid : jids) {
+ Conversation conversation = mXmppConnectionService.find(account,jid);
+ if (conversation != null) {
+ mXmppConnectionService.markRead(conversation);
+ }
+ }
+ }
}
// Update the UI
mXmppConnectionService.updateBlocklistUi(OnUpdateBlocklist.Status.BLOCKED);
diff --git a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java
index 1e50ce9c..63d5782b 100644
--- a/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java
+++ b/src/main/java/eu/siacs/conversations/persistance/DatabaseBackend.java
@@ -55,7 +55,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
private static DatabaseBackend instance = null;
private static final String DATABASE_NAME = "history";
- private static final int DATABASE_VERSION = 31;
+ private static final int DATABASE_VERSION = 33;
private static String CREATE_CONTATCS_STATEMENT = "create table "
+ Contact.TABLENAME + "(" + Contact.ACCOUNT + " TEXT, "
@@ -132,6 +132,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
+ SQLiteAxolotlStore.CERTIFICATE + " BLOB, "
+ SQLiteAxolotlStore.TRUST + " TEXT, "
+ SQLiteAxolotlStore.ACTIVE + " NUMBER, "
+ + SQLiteAxolotlStore.LAST_ACTIVATION + " NUMBER,"
+ SQLiteAxolotlStore.KEY + " TEXT, FOREIGN KEY("
+ SQLiteAxolotlStore.ACCOUNT
+ ") REFERENCES " + Account.TABLENAME + "(" + Account.UUID + ") ON DELETE CASCADE, "
@@ -361,12 +362,12 @@ public class DatabaseBackend extends SQLiteOpenHelper {
db.execSQL("ALTER TABLE "+ SQLiteAxolotlStore.IDENTITIES_TABLENAME + " ADD COLUMN "+SQLiteAxolotlStore.TRUST + " TEXT");
db.execSQL("ALTER TABLE "+ SQLiteAxolotlStore.IDENTITIES_TABLENAME + " ADD COLUMN "+SQLiteAxolotlStore.ACTIVE + " NUMBER");
HashMap<Integer,ContentValues> migration = new HashMap<>();
- migration.put(0,createFingerprintStatusContentValues(FingerprintStatus.Trust.UNDECIDED,true));
+ migration.put(0,createFingerprintStatusContentValues(FingerprintStatus.Trust.TRUSTED,true));
migration.put(1,createFingerprintStatusContentValues(FingerprintStatus.Trust.TRUSTED, true));
migration.put(2,createFingerprintStatusContentValues(FingerprintStatus.Trust.UNTRUSTED, true));
migration.put(3,createFingerprintStatusContentValues(FingerprintStatus.Trust.COMPROMISED, false));
migration.put(4,createFingerprintStatusContentValues(FingerprintStatus.Trust.TRUSTED, false));
- migration.put(5,createFingerprintStatusContentValues(FingerprintStatus.Trust.UNDECIDED, false));
+ migration.put(5,createFingerprintStatusContentValues(FingerprintStatus.Trust.TRUSTED, false));
migration.put(6,createFingerprintStatusContentValues(FingerprintStatus.Trust.UNTRUSTED, false));
migration.put(7,createFingerprintStatusContentValues(FingerprintStatus.Trust.VERIFIED_X509, true));
migration.put(8,createFingerprintStatusContentValues(FingerprintStatus.Trust.VERIFIED_X509, false));
@@ -377,6 +378,16 @@ public class DatabaseBackend extends SQLiteOpenHelper {
}
}
+ if (oldVersion < 32 && newVersion >= 32) {
+ db.execSQL("ALTER TABLE "+ SQLiteAxolotlStore.IDENTITIES_TABLENAME + " ADD COLUMN "+SQLiteAxolotlStore.LAST_ACTIVATION + " NUMBER");
+ ContentValues defaults = new ContentValues();
+ defaults.put(SQLiteAxolotlStore.LAST_ACTIVATION,System.currentTimeMillis());
+ db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME,defaults,null,null);
+ }
+ if (oldVersion < 33 && newVersion >= 33) {
+ String whereClause = SQLiteAxolotlStore.OWN+"=1";
+ db.update(SQLiteAxolotlStore.IDENTITIES_TABLENAME,createFingerprintStatusContentValues(FingerprintStatus.Trust.VERIFIED,true),whereClause,null);
+ }
}
private static ContentValues createFingerprintStatusContentValues(FingerprintStatus.Trust trust, boolean active) {
@@ -780,6 +791,20 @@ public class DatabaseBackend extends SQLiteOpenHelper {
}
}
+ public long getLastTimeFingerprintUsed(Account account, String fingerprint) {
+ String SQL = "select messages.timeSent from accounts join conversations on accounts.uuid=conversations.accountUuid join messages on conversations.uuid=messages.conversationUuid where accounts.uuid=? and messages.axolotl_fingerprint=? order by messages.timesent desc limit 1";
+ String[] args = {account.getUuid(), fingerprint};
+ Cursor cursor = getReadableDatabase().rawQuery(SQL,args);
+ long time;
+ if (cursor.moveToFirst()) {
+ time = cursor.getLong(0);
+ } else {
+ time = 0;
+ }
+ cursor.close();
+ return time;
+ }
+
public Pair<Long,String> getLastClearDate(Account account) {
SQLiteDatabase db = this.getReadableDatabase();
String[] columns = {Conversation.ATTRIBUTES};
@@ -1046,6 +1071,7 @@ public class DatabaseBackend extends SQLiteOpenHelper {
private Cursor getIdentityKeyCursor(SQLiteDatabase db, Account account, String name, Boolean own, String fingerprint) {
String[] columns = {SQLiteAxolotlStore.TRUST,
SQLiteAxolotlStore.ACTIVE,
+ SQLiteAxolotlStore.LAST_ACTIVATION,
SQLiteAxolotlStore.KEY};
ArrayList<String> selectionArgs = new ArrayList<>(4);
selectionArgs.add(account.getUuid());
@@ -1306,9 +1332,14 @@ public class DatabaseBackend extends SQLiteOpenHelper {
return count >= Config.FREQUENT_RESTARTS_THRESHOLD;
}
- public void clearStartTimeCounter() {
- Log.d(Config.LOGTAG,"resetting start time counter");
+ public void clearStartTimeCounter(boolean justOne) {
SQLiteDatabase db = this.getWritableDatabase();
- db.execSQL("delete from "+START_TIMES_TABLE);
+ if (justOne) {
+ db.execSQL("delete from "+START_TIMES_TABLE+" where timestamp in (select timestamp from "+START_TIMES_TABLE+" order by timestamp desc limit 1)");
+ Log.d(Config.LOGTAG,"do not count start up after being swiped away");
+ } else {
+ Log.d(Config.LOGTAG,"resetting start time counter");
+ db.execSQL("delete from " + START_TIMES_TABLE);
+ }
}
}
diff --git a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java
index 1886d3c0..84330d16 100644
--- a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java
+++ b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java
@@ -60,7 +60,7 @@ import eu.siacs.conversations.xmpp.pep.Avatar;
public class FileBackend {
private static final SimpleDateFormat IMAGE_DATE_FORMAT = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US);
- public static final String CONVERSATIONS_FILE_PROVIDER = "eu.siacs.conversations.files";
+ private static final String FILE_PROVIDER = ".files";
private XmppConnectionService mXmppConnectionService;
@@ -152,14 +152,26 @@ public class FileBackend {
return true;
}
- public static String getConversationsFileDirectory() {
- return Environment.getExternalStorageDirectory().getAbsolutePath()+"/Conversations/";
+ public String getConversationsFileDirectory() {
+ if (Config.ONLY_INTERNAL_STORAGE) {
+ return mXmppConnectionService.getFilesDir().getAbsolutePath() + "/Files/";
+ } else {
+ return Environment.getExternalStorageDirectory().getAbsolutePath() + "/Conversations/";
+ }
+ }
+
+ public String getConversationsImageDirectory() {
+ if (Config.ONLY_INTERNAL_STORAGE) {
+ return mXmppConnectionService.getFilesDir().getAbsolutePath()+"/Pictures/";
+ } else {
+ return Environment.getExternalStoragePublicDirectory(
+ Environment.DIRECTORY_PICTURES).getAbsolutePath()
+ + "/Conversations/";
+ }
}
- public static String getConversationsImageDirectory() {
- return Environment.getExternalStoragePublicDirectory(
- Environment.DIRECTORY_PICTURES).getAbsolutePath()
- + "/Conversations/";
+ public static String getConversationsLogsDirectory() {
+ return Environment.getExternalStorageDirectory().getAbsolutePath()+"/Conversations/";
}
public Bitmap resize(Bitmap originalBitmap, int size) {
@@ -451,17 +463,27 @@ public class FileBackend {
}
public Uri getTakePhotoUri() {
- File file = new File(getTakePhotoPath()+"IMG_" + this.IMAGE_DATE_FORMAT.format(new Date()) + ".jpg");
+ File file;
+ if (Config.ONLY_INTERNAL_STORAGE) {
+ file = new File(mXmppConnectionService.getCacheDir().getAbsolutePath(), "Camera/IMG_" + this.IMAGE_DATE_FORMAT.format(new Date()) + ".jpg");
+ } else {
+ file = new File(getTakePhotoPath() + "IMG_" + this.IMAGE_DATE_FORMAT.format(new Date()) + ".jpg");
+ }
file.getParentFile().mkdirs();
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
- return FileProvider.getUriForFile(mXmppConnectionService, CONVERSATIONS_FILE_PROVIDER, file);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N || Config.ONLY_INTERNAL_STORAGE) {
+ return getUriForFile(mXmppConnectionService,file);
} else {
return Uri.fromFile(file);
}
}
+ public static Uri getUriForFile(Context context, File file) {
+ String packageId = context.getPackageName();
+ return FileProvider.getUriForFile(context, packageId + FILE_PROVIDER, file);
+ }
+
public static Uri getIndexableTakePhotoUri(Uri original) {
- if ("file".equals(original.getScheme())) {
+ if (Config.ONLY_INTERNAL_STORAGE || "file".equals(original.getScheme())) {
return original;
} else {
List<String> segments = original.getPathSegments();
@@ -707,8 +729,7 @@ public class FileBackend {
}
public Uri getJingleFileUri(Message message) {
- File file = getFile(message);
- return Uri.parse("file://" + file.getAbsolutePath());
+ return getUriForFile(mXmppConnectionService,getFile(message));
}
public void updateFileParams(Message message) {
diff --git a/src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java b/src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java
index 8d02f975..dfe4cb28 100644
--- a/src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java
+++ b/src/main/java/eu/siacs/conversations/services/AbstractConnectionManager.java
@@ -55,7 +55,7 @@ public class AbstractConnectionManager {
}
public boolean hasStoragePermission() {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ if (!Config.ONLY_INTERNAL_STORAGE && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
return mXmppConnectionService.checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;
} else {
return true;
diff --git a/src/main/java/eu/siacs/conversations/services/AvatarService.java b/src/main/java/eu/siacs/conversations/services/AvatarService.java
index 45a6fd81..4b4d1ed3 100644
--- a/src/main/java/eu/siacs/conversations/services/AvatarService.java
+++ b/src/main/java/eu/siacs/conversations/services/AvatarService.java
@@ -44,6 +44,9 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded {
}
private Bitmap get(final Contact contact, final int size, boolean cachedOnly) {
+ if (contact.isSelf()) {
+ return get(contact.getAccount(),size,cachedOnly);
+ }
final String KEY = key(contact, size);
Bitmap avatar = this.mXmppConnectionService.getBitmapCache().get(KEY);
if (avatar != null || cachedOnly) {
@@ -169,7 +172,7 @@ public class AvatarService implements OnAdvancedStreamFeaturesLoaded {
if (bitmap != null || cachedOnly) {
return bitmap;
}
- final List<MucOptions.User> users = mucOptions.getUsers();
+ final List<MucOptions.User> users = mucOptions.getUsers(5);
int count = users.size();
bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
diff --git a/src/main/java/eu/siacs/conversations/services/BarcodeProvider.java b/src/main/java/eu/siacs/conversations/services/BarcodeProvider.java
new file mode 100644
index 00000000..9c50b081
--- /dev/null
+++ b/src/main/java/eu/siacs/conversations/services/BarcodeProvider.java
@@ -0,0 +1,206 @@
+package eu.siacs.conversations.services;
+
+import android.content.ComponentName;
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.net.Uri;
+import android.os.CancellationSignal;
+import android.os.IBinder;
+import android.os.ParcelFileDescriptor;
+import android.support.annotation.Nullable;
+import android.util.Log;
+
+import com.google.zxing.BarcodeFormat;
+import com.google.zxing.EncodeHintType;
+import com.google.zxing.aztec.AztecWriter;
+import com.google.zxing.common.BitMatrix;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.OutputStream;
+import java.util.Hashtable;
+
+import eu.siacs.conversations.Config;
+import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.utils.CryptoHelper;
+import eu.siacs.conversations.xmpp.jid.Jid;
+
+public class BarcodeProvider extends ContentProvider implements ServiceConnection {
+
+ private static final String AUTHORITY = ".barcodes";
+
+ private final Object lock = new Object();
+
+ private XmppConnectionService mXmppConnectionService;
+ private boolean mBindingInProcess = false;
+
+ @Override
+ public boolean onCreate() {
+ File barcodeDirectory = new File(getContext().getCacheDir().getAbsolutePath() + "/barcodes/");
+ if (barcodeDirectory.exists() && barcodeDirectory.isDirectory()) {
+ for (File file : barcodeDirectory.listFiles()) {
+ if (file.isFile() && !file.isHidden()) {
+ Log.d(Config.LOGTAG, "deleting old barcode file " + file.getAbsolutePath());
+ file.delete();
+ }
+ }
+ }
+ return true;
+ }
+
+ @Nullable
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
+ return null;
+ }
+
+ @Nullable
+ @Override
+ public String getType(Uri uri) {
+ return "image/png";
+ }
+
+ @Nullable
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ return null;
+ }
+
+ @Override
+ public int delete(Uri uri, String selection, String[] selectionArgs) {
+ return 0;
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ return 0;
+ }
+
+ @Override
+ public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
+ return openFile(uri, mode, null);
+ }
+
+ @Override
+ public ParcelFileDescriptor openFile(Uri uri, String mode, CancellationSignal signal) throws FileNotFoundException {
+ Log.d(Config.LOGTAG, "opening file with uri (normal): " + uri.toString());
+ String path = uri.getPath();
+ if (path != null && path.endsWith(".png") && path.length() >= 5) {
+ String jid = path.substring(1).substring(0, path.length() - 4);
+ Log.d(Config.LOGTAG, "account:" + jid);
+ if (connectAndWait()) {
+ Log.d(Config.LOGTAG, "connected to background service");
+ try {
+ Account account = mXmppConnectionService.findAccountByJid(Jid.fromString(jid));
+ if (account != null) {
+ String shareableUri = account.getShareableUri();
+ String hash = CryptoHelper.getFingerprint(shareableUri);
+ File file = new File(getContext().getCacheDir().getAbsolutePath() + "/barcodes/" + hash);
+ if (!file.exists()) {
+ file.getParentFile().mkdirs();
+ file.createNewFile();
+ Bitmap bitmap = createAztecBitmap(account.getShareableUri(), 1024);
+ OutputStream outputStream = new FileOutputStream(file);
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream);
+ outputStream.close();
+ outputStream.flush();
+ }
+ return ParcelFileDescriptor.open(file,ParcelFileDescriptor.MODE_READ_ONLY);
+ }
+ } catch (Exception e) {
+ throw new FileNotFoundException();
+ }
+ }
+ }
+ throw new FileNotFoundException();
+ }
+
+ private boolean connectAndWait() {
+ Intent intent = new Intent(getContext(), XmppConnectionService.class);
+ intent.setAction(this.getClass().getSimpleName());
+ Context context = getContext();
+ if (context != null) {
+ synchronized (this) {
+ if (mXmppConnectionService == null && !mBindingInProcess) {
+ Log.d(Config.LOGTAG,"calling to bind service");
+ context.startService(intent);
+ context.bindService(intent, this, Context.BIND_AUTO_CREATE);
+ this.mBindingInProcess = true;
+ }
+ }
+ try {
+ waitForService();
+ return true;
+ } catch (InterruptedException e) {
+ return false;
+ }
+ } else {
+ Log.d(Config.LOGTAG, "context was null");
+ return false;
+ }
+ }
+
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ synchronized (this) {
+ XmppConnectionService.XmppConnectionBinder binder = (XmppConnectionService.XmppConnectionBinder) service;
+ mXmppConnectionService = binder.getService();
+ mBindingInProcess = false;
+ synchronized (this.lock) {
+ lock.notifyAll();
+ }
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ synchronized (this) {
+ mXmppConnectionService = null;
+ }
+ }
+
+ private void waitForService() throws InterruptedException {
+ if (mXmppConnectionService == null) {
+ synchronized (this.lock) {
+ lock.wait();
+ }
+ } else {
+ Log.d(Config.LOGTAG,"not waiting for service because already initialized");
+ }
+ }
+
+ public static Uri getUriForAccount(Context context, Account account) {
+ final String packageId = context.getPackageName();
+ return Uri.parse("content://" + packageId + AUTHORITY + "/" + account.getJid().toBareJid() + ".png");
+ }
+
+ public static Bitmap createAztecBitmap(String input, int size) {
+ try {
+ final AztecWriter AZTEC_WRITER = new AztecWriter();
+ final Hashtable<EncodeHintType, Object> hints = new Hashtable<>();
+ hints.put(EncodeHintType.ERROR_CORRECTION, 10);
+ final BitMatrix result = AZTEC_WRITER.encode(input, BarcodeFormat.AZTEC, size, size, hints);
+ final int width = result.getWidth();
+ final int height = result.getHeight();
+ final int[] pixels = new int[width * height];
+ for (int y = 0; y < height; y++) {
+ final int offset = y * width;
+ for (int x = 0; x < width; x++) {
+ pixels[offset + x] = result.get(x, y) ? Color.BLACK : Color.WHITE;
+ }
+ }
+ final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
+ return bitmap;
+ } catch (final Exception e) {
+ return null;
+ }
+ }
+}
diff --git a/src/main/java/eu/siacs/conversations/services/ExportLogsService.java b/src/main/java/eu/siacs/conversations/services/ExportLogsService.java
index 87e65931..51eced05 100644
--- a/src/main/java/eu/siacs/conversations/services/ExportLogsService.java
+++ b/src/main/java/eu/siacs/conversations/services/ExportLogsService.java
@@ -26,7 +26,7 @@ import eu.siacs.conversations.xmpp.jid.Jid;
public class ExportLogsService extends Service {
private static final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss");
- private static final String DIRECTORY_STRING_FORMAT = FileBackend.getConversationsFileDirectory() + "/logs/%s";
+ private static final String DIRECTORY_STRING_FORMAT = FileBackend.getConversationsLogsDirectory() + "/logs/%s";
private static final String MESSAGE_STRING_FORMAT = "(%s) %s: %s\n";
private static final int NOTIFICATION_ID = 1;
private static AtomicBoolean running = new AtomicBoolean(false);
diff --git a/src/main/java/eu/siacs/conversations/services/NotificationService.java b/src/main/java/eu/siacs/conversations/services/NotificationService.java
index 6e1d6c4b..47364b30 100644
--- a/src/main/java/eu/siacs/conversations/services/NotificationService.java
+++ b/src/main/java/eu/siacs/conversations/services/NotificationService.java
@@ -34,6 +34,7 @@ import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.ui.ConversationActivity;
import eu.siacs.conversations.ui.ManageAccountActivity;
+import eu.siacs.conversations.ui.SettingsActivity;
import eu.siacs.conversations.ui.TimePreference;
import eu.siacs.conversations.utils.GeoHelper;
import eu.siacs.conversations.utils.UIHelper;
@@ -591,7 +592,7 @@ public class NotificationService {
errors.add(account);
}
}
- if (mXmppConnectionService.getPreferences().getBoolean("keep_foreground_service", false)) {
+ if (mXmppConnectionService.getPreferences().getBoolean(SettingsActivity.KEEP_FOREGROUND_SERVICE, false)) {
notificationManager.notify(FOREGROUND_NOTIFICATION_ID, createForegroundNotification());
}
final NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(mXmppConnectionService);
diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
index 1321485d..b9aeffee 100644
--- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
+++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java
@@ -67,7 +67,6 @@ import eu.siacs.conversations.crypto.PgpEngine;
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
import eu.siacs.conversations.crypto.axolotl.FingerprintStatus;
import eu.siacs.conversations.crypto.axolotl.XmppAxolotlMessage;
-import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Blockable;
import eu.siacs.conversations.entities.Bookmark;
@@ -94,6 +93,7 @@ import eu.siacs.conversations.parser.MessageParser;
import eu.siacs.conversations.parser.PresenceParser;
import eu.siacs.conversations.persistance.DatabaseBackend;
import eu.siacs.conversations.persistance.FileBackend;
+import eu.siacs.conversations.ui.SettingsActivity;
import eu.siacs.conversations.ui.UiCallback;
import eu.siacs.conversations.utils.ConversationsFileObserver;
import eu.siacs.conversations.utils.CryptoHelper;
@@ -335,32 +335,34 @@ public class XmppConnectionService extends Service {
}
account.pendingConferenceJoins.clear();
scheduleWakeUpCall(Config.PING_MAX_INTERVAL, account.getUuid().hashCode());
- } else if (account.getStatus() == Account.State.OFFLINE || account.getStatus() == Account.State.DISABLED) {
- resetSendingToWaiting(account);
- if (!account.isOptionSet(Account.OPTION_DISABLED)) {
- synchronized (mLowPingTimeoutMode) {
- if (mLowPingTimeoutMode.contains(account.getJid().toBareJid())) {
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": went into offline state during low ping mode. reconnecting now");
- reconnectAccount(account, true, false);
- } else {
- int timeToReconnect = mRandom.nextInt(20) + 10;
- scheduleWakeUpCall(timeToReconnect, account.getUuid().hashCode());
+ } else {
+ if (account.getStatus() == Account.State.OFFLINE || account.getStatus() == Account.State.DISABLED) {
+ resetSendingToWaiting(account);
+ if (!account.isOptionSet(Account.OPTION_DISABLED)) {
+ synchronized (mLowPingTimeoutMode) {
+ if (mLowPingTimeoutMode.contains(account.getJid().toBareJid())) {
+ Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": went into offline state during low ping mode. reconnecting now");
+ reconnectAccount(account, true, false);
+ } else {
+ int timeToReconnect = mRandom.nextInt(10) + 2;
+ scheduleWakeUpCall(timeToReconnect, account.getUuid().hashCode());
+ }
}
}
- }
- } else if (account.getStatus() == Account.State.REGISTRATION_SUCCESSFUL) {
- databaseBackend.updateAccount(account);
- reconnectAccount(account, true, false);
- } else if ((account.getStatus() != Account.State.CONNECTING)
- && (account.getStatus() != Account.State.NO_INTERNET)) {
- resetSendingToWaiting(account);
- if (connection != null) {
- int next = connection.getTimeToNextAttempt();
- Log.d(Config.LOGTAG, account.getJid().toBareJid()
- + ": error connecting account. try again in "
- + next + "s for the "
- + (connection.getAttempt() + 1) + " time");
- scheduleWakeUpCall(next, account.getUuid().hashCode());
+ } else if (account.getStatus() == Account.State.REGISTRATION_SUCCESSFUL) {
+ databaseBackend.updateAccount(account);
+ reconnectAccount(account, true, false);
+ } else if ((account.getStatus() != Account.State.CONNECTING)
+ && (account.getStatus() != Account.State.NO_INTERNET)) {
+ resetSendingToWaiting(account);
+ if (connection != null) {
+ int next = connection.getTimeToNextAttempt();
+ Log.d(Config.LOGTAG, account.getJid().toBareJid()
+ + ": error connecting account. try again in "
+ + next + "s for the "
+ + (connection.getAttempt() + 1) + " time");
+ scheduleWakeUpCall(next, account.getUuid().hashCode());
+ }
}
}
getNotificationService().updateErrorNotification();
@@ -547,7 +549,7 @@ public class XmppConnectionService extends Service {
switch (action) {
case ConnectivityManager.CONNECTIVITY_ACTION:
if (hasInternetConnection() && Config.RESET_ATTEMPT_COUNT_ON_NETWORK_CHANGE) {
- resetAllAttemptCounts(true);
+ resetAllAttemptCounts(true, false);
}
break;
case ACTION_MERGE_PHONE_CONTACTS:
@@ -566,14 +568,14 @@ public class XmppConnectionService extends Service {
}
break;
case ACTION_DISABLE_FOREGROUND:
- getPreferences().edit().putBoolean("keep_foreground_service", false).commit();
+ getPreferences().edit().putBoolean(SettingsActivity.KEEP_FOREGROUND_SERVICE, false).commit();
toggleForegroundService();
break;
case ACTION_DISMISS_ERROR_NOTIFICATIONS:
dismissErrorNotifications();
break;
case ACTION_TRY_AGAIN:
- resetAllAttemptCounts(false);
+ resetAllAttemptCounts(false, true);
interactive = true;
break;
case ACTION_REPLY_TO_CONVERSATION:
@@ -611,103 +613,108 @@ public class XmppConnectionService extends Service {
break;
}
}
- this.wakeLock.acquire();
+ synchronized (this) {
+ this.wakeLock.acquire();
+ boolean pingNow = ConnectivityManager.CONNECTIVITY_ACTION.equals(action);
+ HashSet<Account> pingCandidates = new HashSet<>();
+ for (Account account : accounts) {
+ pingNow |= processAccountState(account,
+ interactive,
+ "ui".equals(action),
+ CryptoHelper.getAccountFingerprint(account).equals(pushedAccountHash),
+ pingCandidates);
+ }
+ if (pingNow) {
+ for (Account account : pingCandidates) {
+ final boolean lowTimeout = mLowPingTimeoutMode.contains(account.getJid().toBareJid());
+ account.getXmppConnection().sendPing();
+ Log.d(Config.LOGTAG, account.getJid().toBareJid() + " send ping (action=" + action + ",lowTimeout=" + Boolean.toString(lowTimeout) + ")");
+ scheduleWakeUpCall(lowTimeout ? Config.LOW_PING_TIMEOUT : Config.PING_TIMEOUT, account.getUuid().hashCode());
+ }
+ }
+ if (wakeLock.isHeld()) {
+ try {
+ wakeLock.release();
+ } catch (final RuntimeException ignored) {
+ }
+ }
+ }
+ return START_STICKY;
+ }
+ private boolean processAccountState(Account account, boolean interactive, boolean isUiAction, boolean isAccountPushed, HashSet<Account> pingCandidates) {
boolean pingNow = false;
- HashSet<Account> pingCandidates = new HashSet<>();
-
- for (Account account : accounts) {
- if (!account.isOptionSet(Account.OPTION_DISABLED)) {
- if (!hasInternetConnection()) {
- account.setStatus(Account.State.NO_INTERNET);
+ if (!account.isOptionSet(Account.OPTION_DISABLED)) {
+ if (!hasInternetConnection()) {
+ account.setStatus(Account.State.NO_INTERNET);
+ if (statusListener != null) {
+ statusListener.onStatusChanged(account);
+ }
+ } else {
+ if (account.getStatus() == Account.State.NO_INTERNET) {
+ account.setStatus(Account.State.OFFLINE);
if (statusListener != null) {
statusListener.onStatusChanged(account);
}
- } else {
- if (account.getStatus() == Account.State.NO_INTERNET) {
- account.setStatus(Account.State.OFFLINE);
- if (statusListener != null) {
- statusListener.onStatusChanged(account);
- }
- }
- if (account.getStatus() == Account.State.ONLINE) {
- synchronized (mLowPingTimeoutMode) {
- long lastReceived = account.getXmppConnection().getLastPacketReceived();
- long lastSent = account.getXmppConnection().getLastPingSent();
- long pingInterval = "ui".equals(action) ? Config.PING_MIN_INTERVAL * 1000 : Config.PING_MAX_INTERVAL * 1000;
- long msToNextPing = (Math.max(lastReceived, lastSent) + pingInterval) - SystemClock.elapsedRealtime();
- int pingTimeout = mLowPingTimeoutMode.contains(account.getJid().toBareJid()) ? Config.LOW_PING_TIMEOUT * 1000 : Config.PING_TIMEOUT * 1000;
- long pingTimeoutIn = (lastSent + pingTimeout) - SystemClock.elapsedRealtime();
- if (lastSent > lastReceived) {
- if (pingTimeoutIn < 0) {
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": ping timeout");
- this.reconnectAccount(account, true, interactive);
- } else {
- int secs = (int) (pingTimeoutIn / 1000);
- this.scheduleWakeUpCall(secs, account.getUuid().hashCode());
+ }
+ if (account.getStatus() == Account.State.ONLINE) {
+ synchronized (mLowPingTimeoutMode) {
+ long lastReceived = account.getXmppConnection().getLastPacketReceived();
+ long lastSent = account.getXmppConnection().getLastPingSent();
+ long pingInterval = isUiAction ? Config.PING_MIN_INTERVAL * 1000 : Config.PING_MAX_INTERVAL * 1000;
+ long msToNextPing = (Math.max(lastReceived, lastSent) + pingInterval) - SystemClock.elapsedRealtime();
+ int pingTimeout = mLowPingTimeoutMode.contains(account.getJid().toBareJid()) ? Config.LOW_PING_TIMEOUT * 1000 : Config.PING_TIMEOUT * 1000;
+ long pingTimeoutIn = (lastSent + pingTimeout) - SystemClock.elapsedRealtime();
+ if (lastSent > lastReceived) {
+ if (pingTimeoutIn < 0) {
+ Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": ping timeout");
+ this.reconnectAccount(account, true, interactive);
+ } else {
+ int secs = (int) (pingTimeoutIn / 1000);
+ this.scheduleWakeUpCall(secs, account.getUuid().hashCode());
+ }
+ } else {
+ pingCandidates.add(account);
+ if (isAccountPushed) {
+ pingNow = true;
+ if (mLowPingTimeoutMode.add(account.getJid().toBareJid())) {
+ Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": entering low ping timeout mode");
}
+ } else if (msToNextPing <= 0) {
+ pingNow = true;
} else {
- pingCandidates.add(account);
- if (CryptoHelper.getAccountFingerprint(account).equals(pushedAccountHash)) {
- pingNow = true;
- if (mLowPingTimeoutMode.add(account.getJid().toBareJid())) {
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": entering low ping timeout mode");
- }
- } else if (msToNextPing <= 0) {
- pingNow = true;
- } else {
- this.scheduleWakeUpCall((int) (msToNextPing / 1000), account.getUuid().hashCode());
- if (mLowPingTimeoutMode.remove(account.getJid().toBareJid())) {
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": leaving low ping timeout mode");
- }
+ this.scheduleWakeUpCall((int) (msToNextPing / 1000), account.getUuid().hashCode());
+ if (mLowPingTimeoutMode.remove(account.getJid().toBareJid())) {
+ Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": leaving low ping timeout mode");
}
}
}
- } else if (account.getStatus() == Account.State.OFFLINE) {
+ }
+ } else if (account.getStatus() == Account.State.OFFLINE) {
+ reconnectAccount(account, true, interactive);
+ } else if (account.getStatus() == Account.State.CONNECTING) {
+ long secondsSinceLastConnect = (SystemClock.elapsedRealtime() - account.getXmppConnection().getLastConnect()) / 1000;
+ long secondsSinceLastDisco = (SystemClock.elapsedRealtime() - account.getXmppConnection().getLastDiscoStarted()) / 1000;
+ long discoTimeout = Config.CONNECT_DISCO_TIMEOUT - secondsSinceLastDisco;
+ long timeout = Config.CONNECT_TIMEOUT - secondsSinceLastConnect;
+ if (timeout < 0) {
+ Log.d(Config.LOGTAG, account.getJid() + ": time out during connect reconnecting (secondsSinceLast="+secondsSinceLastConnect+")");
+ account.getXmppConnection().resetAttemptCount(false);
reconnectAccount(account, true, interactive);
- } else if (account.getStatus() == Account.State.CONNECTING) {
- long secondsSinceLastConnect = (SystemClock.elapsedRealtime() - account.getXmppConnection().getLastConnect()) / 1000;
- long secondsSinceLastDisco = (SystemClock.elapsedRealtime() - account.getXmppConnection().getLastDiscoStarted()) / 1000;
- long discoTimeout = Config.CONNECT_DISCO_TIMEOUT - secondsSinceLastDisco;
- long timeout = Config.CONNECT_TIMEOUT - secondsSinceLastConnect;
- if (timeout < 0) {
- Log.d(Config.LOGTAG, account.getJid() + ": time out during connect reconnecting");
- account.getXmppConnection().resetAttemptCount();
- reconnectAccount(account, true, interactive);
- } else if (discoTimeout < 0) {
- account.getXmppConnection().sendDiscoTimeout();
- scheduleWakeUpCall((int) Math.min(timeout,discoTimeout), account.getUuid().hashCode());
- } else {
- scheduleWakeUpCall((int) Math.min(timeout,discoTimeout), account.getUuid().hashCode());
- }
+ } else if (discoTimeout < 0) {
+ account.getXmppConnection().sendDiscoTimeout();
+ scheduleWakeUpCall((int) Math.min(timeout,discoTimeout), account.getUuid().hashCode());
} else {
- if (account.getXmppConnection().getTimeToNextAttempt() <= 0) {
- reconnectAccount(account, true, interactive);
- }
+ scheduleWakeUpCall((int) Math.min(timeout,discoTimeout), account.getUuid().hashCode());
+ }
+ } else {
+ if (account.getXmppConnection().getTimeToNextAttempt() <= 0) {
+ reconnectAccount(account, true, interactive);
}
}
- if (mOnAccountUpdate != null) {
- mOnAccountUpdate.onAccountUpdate();
- }
- }
- }
- if (pingNow) {
- for (Account account : pingCandidates) {
- synchronized (mLowPingTimeoutMode) {
- final boolean lowTimeout = mLowPingTimeoutMode.contains(account.getJid().toBareJid());
- account.getXmppConnection().sendPing();
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + " send ping (action=" + action + ",lowTimeout=" + Boolean.toString(lowTimeout) + ")");
- scheduleWakeUpCall(lowTimeout ? Config.LOW_PING_TIMEOUT : Config.PING_TIMEOUT, account.getUuid().hashCode());
- }
- }
- }
- if (wakeLock.isHeld()) {
- try {
- wakeLock.release();
- } catch (final RuntimeException ignored) {
}
}
- return START_STICKY;
+ return pingNow;
}
public boolean isDataSaverDisabled() {
@@ -761,15 +768,15 @@ public class XmppConnectionService extends Service {
}
private boolean manuallyChangePresence() {
- return getPreferences().getBoolean("manually_change_presence", false);
+ return getPreferences().getBoolean(SettingsActivity.MANUALLY_CHANGE_PRESENCE, false);
}
private boolean treatVibrateAsSilent() {
- return getPreferences().getBoolean("treat_vibrate_as_silent", false);
+ return getPreferences().getBoolean(SettingsActivity.TREAT_VIBRATE_AS_SILENT, false);
}
private boolean awayWhenScreenOff() {
- return getPreferences().getBoolean("away_when_screen_off", false);
+ return getPreferences().getBoolean(SettingsActivity.AWAY_WHEN_SCREEN_IS_OFF, false);
}
private String getCompressPicturesPreference() {
@@ -814,13 +821,13 @@ public class XmppConnectionService extends Service {
}
}
- private void resetAllAttemptCounts(boolean reallyAll) {
+ private void resetAllAttemptCounts(boolean reallyAll, boolean retryImmediately) {
Log.d(Config.LOGTAG, "resetting all attempt counts");
for (Account account : accounts) {
if (account.hasErrorStatus() || reallyAll) {
final XmppConnection connection = account.getXmppConnection();
if (connection != null) {
- connection.resetAttemptCount();
+ connection.resetAttemptCount(retryImmediately);
}
}
if (account.setShowErrorNotification(true)) {
@@ -868,7 +875,7 @@ public class XmppConnectionService extends Service {
this.accounts = databaseBackend.getAccounts();
if (!keepForegroundService() && databaseBackend.startTimeCountExceedsThreshold()) {
- getPreferences().edit().putBoolean("keep_foreground_service",true).commit();
+ getPreferences().edit().putBoolean(SettingsActivity.KEEP_FOREGROUND_SERVICE,true).commit();
Log.d(Config.LOGTAG,"number of restarts exceeds threshold. enabling foreground service");
}
@@ -957,7 +964,7 @@ public class XmppConnectionService extends Service {
}
private boolean keepForegroundService() {
- return getPreferences().getBoolean("keep_foreground_service",false);
+ return getPreferences().getBoolean(SettingsActivity.KEEP_FOREGROUND_SERVICE,false);
}
@Override
@@ -972,7 +979,7 @@ public class XmppConnectionService extends Service {
private void logoutAndSave(boolean stop) {
int activeAccounts = 0;
- databaseBackend.clearStartTimeCounter(); // regular swipes don't count towards restart counter
+ databaseBackend.clearStartTimeCounter(true); // regular swipes don't count towards restart counter
for (final Account account : accounts) {
if (account.getStatus() != Account.State.DISABLED) {
activeAccounts++;
@@ -2890,8 +2897,6 @@ public class XmppConnectionService extends Service {
if (connection == null) {
connection = createConnection(account);
account.setXmppConnection(connection);
- } else {
- connection.interrupt();
}
if (!account.isOptionSet(Account.OPTION_DISABLED)) {
if (!force) {
@@ -2900,10 +2905,11 @@ public class XmppConnectionService extends Service {
Thread thread = new Thread(connection);
connection.setInteractive(interactive);
connection.prepareNewConnection();
+ connection.interrupt();
thread.start();
scheduleWakeUpCall(Config.CONNECT_DISCO_TIMEOUT, account.getUuid().hashCode());
} else {
- disconnect(account, force);
+ disconnect(account, force || account.getTrueStatus().isError());
account.getRoster().clearPresences();
connection.resetEverything();
account.getAxolotlService().resetBrokenness();
@@ -3606,22 +3612,25 @@ public class XmppConnectionService extends Service {
mDatabaseExecutor.execute(new Runnable() {
@Override
public void run() {
- databaseBackend.clearStartTimeCounter();
+ databaseBackend.clearStartTimeCounter(false);
}
});
}
- public void verifyFingerprints(Contact contact, List<XmppUri.Fingerprint> fingerprints) {
+ public boolean verifyFingerprints(Contact contact, List<XmppUri.Fingerprint> fingerprints) {
boolean needsRosterWrite = false;
+ boolean performedVerification = false;
final AxolotlService axolotlService = contact.getAccount().getAxolotlService();
for(XmppUri.Fingerprint fp : fingerprints) {
if (fp.type == XmppUri.FingerprintType.OTR) {
- needsRosterWrite |= contact.addOtrFingerprint(fp.fingerprint);
+ 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) {
if (!fingerprintStatus.isVerified()) {
+ performedVerification = true;
axolotlService.setFingerprintTrust(fingerprint,fingerprintStatus.toVerified());
}
} else {
@@ -3632,6 +3641,7 @@ public class XmppConnectionService extends Service {
if (needsRosterWrite) {
syncRosterToDisk(contact.getAccount());
}
+ return performedVerification;
}
public boolean verifyFingerprints(Account account, List<XmppUri.Fingerprint> fingerprints) {
@@ -3656,6 +3666,10 @@ public class XmppConnectionService extends Service {
return verifiedSomething;
}
+ public boolean blindTrustBeforeVerification() {
+ return getPreferences().getBoolean(SettingsActivity.BLIND_TRUST_BEFORE_VERIFICATION, true);
+ }
+
public interface OnMamPreferencesFetched {
void onPreferencesFetched(Element prefs);
void onPreferencesFetchFailed();
diff --git a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java
index 7791372a..42eb49fb 100644
--- a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java
@@ -380,7 +380,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
MenuItem invite = menu.findItem(R.id.invite);
startConversation.setVisible(true);
if (contact != null) {
- showContactDetails.setVisible(true);
+ showContactDetails.setVisible(!contact.isSelf());
}
if (user.getRole() == MucOptions.Role.NONE) {
invite.setVisible(true);
diff --git a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java
index a567f151..296a10c2 100644
--- a/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ContactDetailsActivity.java
@@ -41,6 +41,7 @@ import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.PgpEngine;
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
import eu.siacs.conversations.crypto.axolotl.FingerprintStatus;
+import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.ListItem;
@@ -48,6 +49,7 @@ import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate;
import eu.siacs.conversations.services.XmppConnectionService.OnRosterUpdate;
import eu.siacs.conversations.utils.CryptoHelper;
import eu.siacs.conversations.utils.UIHelper;
+import eu.siacs.conversations.utils.XmppUri;
import eu.siacs.conversations.xmpp.OnKeyStatusUpdated;
import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
import eu.siacs.conversations.xmpp.XmppConnection;
@@ -445,9 +447,12 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
}
}
if (Config.supportOmemo()) {
- for (final String fingerprint : contact.getAccount().getAxolotlService().getFingerprintsForContact(contact)) {
- boolean highlight = fingerprint.equals(messageFingerprint);
- hasKeys |= addFingerprintRow(keys, contact.getAccount(), fingerprint, highlight);
+ for (final XmppAxolotlSession session : contact.getAccount().getAxolotlService().findSessionsForContact(contact)) {
+ if (!session.getTrust().isCompromised()) {
+ boolean highlight = session.getFingerprint().equals(messageFingerprint);
+ hasKeys = true;
+ addFingerprintRow(keys, session, highlight);
+ }
}
}
if (Config.supportOpenPgp() && contact.getPgpKeyId() != 0) {
@@ -524,13 +529,16 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
}
public void onBackendConnected() {
- if ((accountJid != null) && (contactJid != null)) {
- Account account = xmppConnectionService
- .findAccountByJid(accountJid);
+ if (accountJid != null && contactJid != null) {
+ Account account = xmppConnectionService.findAccountByJid(accountJid);
if (account == null) {
return;
}
this.contact = account.getRoster().getContact(contactJid);
+ if (mPendingFingerprintVerificationUri != null) {
+ processFingerprintVerification(mPendingFingerprintVerificationUri);
+ mPendingFingerprintVerificationUri = null;
+ }
populateView();
}
}
@@ -539,4 +547,15 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
public void onKeyStatusUpdated(AxolotlService.FetchStatus report) {
refreshUi();
}
+
+ @Override
+ protected void processFingerprintVerification(XmppUri uri) {
+ if (contact != null && contact.getJid().toBareJid().equals(uri.getJid()) && uri.hasFingerprints()) {
+ if (xmppConnectionService.verifyFingerprints(contact,uri.getFingerprints())) {
+ Toast.makeText(this,R.string.verified_fingerprints,Toast.LENGTH_SHORT).show();
+ }
+ } else {
+ Toast.makeText(this,R.string.invalid_barcode,Toast.LENGTH_SHORT).show();
+ }
+ }
}
diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java
index feabae11..d298aa28 100644
--- a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java
@@ -497,6 +497,7 @@ public class ConversationActivity extends XmppActivity
case ATTACHMENT_CHOICE_TAKE_PHOTO:
Uri uri = xmppConnectionService.getFileBackend().getTakePhotoUri();
intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
mPendingImageUris.clear();
@@ -551,7 +552,7 @@ public class ConversationActivity extends XmppActivity
public void attachFile(final int attachmentChoice) {
if (attachmentChoice != ATTACHMENT_CHOICE_LOCATION) {
- if (!hasStoragePermission(attachmentChoice)) {
+ if (!Config.ONLY_INTERNAL_STORAGE && !hasStoragePermission(attachmentChoice)) {
return;
}
}
@@ -648,7 +649,7 @@ public class ConversationActivity extends XmppActivity
}
public void startDownloadable(Message message) {
- if (!hasStoragePermission(ConversationActivity.REQUEST_START_DOWNLOAD)) {
+ if (!Config.ONLY_INTERNAL_STORAGE && !hasStoragePermission(ConversationActivity.REQUEST_START_DOWNLOAD)) {
this.mPendingDownloadableMessage = message;
return;
}
@@ -1417,9 +1418,11 @@ public class ConversationActivity extends XmppActivity
attachImageToConversation(getSelectedConversation(), uri);
mPendingImageUris.clear();
}
- Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
- intent.setData(uri);
- sendBroadcast(intent);
+ if (!Config.ONLY_INTERNAL_STORAGE) {
+ Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
+ intent.setData(uri);
+ sendBroadcast(intent);
+ }
} else {
mPendingImageUris.clear();
}
diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java
index 54b60949..761f8054 100644
--- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java
+++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java
@@ -683,8 +683,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
shareIntent.setType("text/plain");
} else {
shareIntent.putExtra(Intent.EXTRA_STREAM,
- activity.xmppConnectionService.getFileBackend()
- .getJingleFileUri(message));
+ activity.xmppConnectionService.getFileBackend().getJingleFileUri(message));
shareIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
String mime = message.getMimeType();
if (mime == null) {
@@ -946,15 +945,15 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
private void updateSnackBar(final Conversation conversation) {
final Account account = conversation.getAccount();
- final Contact contact = conversation.getContact();
final int mode = conversation.getMode();
+ final Contact contact = mode == Conversation.MODE_SINGLE ? conversation.getContact() : null;
if (account.getStatus() == Account.State.DISABLED) {
showSnackbar(R.string.this_account_is_disabled, R.string.enable, this.mEnableAccountListener);
} else if (conversation.isBlocked()) {
showSnackbar(R.string.contact_blocked, R.string.unblock, this.mUnblockClickListener);
- } else if (!contact.showInRoster() && contact.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) {
+ } else if (contact != null && !contact.showInRoster() && contact.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) {
showSnackbar(R.string.contact_added_you, R.string.add_back, this.mAddBackClickListener);
- } else if (contact.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) {
+ } else if (contact != null && contact.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) {
showSnackbar(R.string.contact_asks_for_presence_subscription, R.string.allow, this.mAllowPresenceSubscription);
} else if (mode == Conversation.MODE_MULTI
&& !conversation.getMucOptions().online()
diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java
index 0f38173b..f48e8a48 100644
--- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java
@@ -44,13 +44,16 @@ import eu.siacs.conversations.Config;
import eu.siacs.conversations.OmemoActivity;
import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
+import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
import eu.siacs.conversations.entities.Account;
+import eu.siacs.conversations.services.BarcodeProvider;
import eu.siacs.conversations.services.XmppConnectionService.OnCaptchaRequested;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate;
import eu.siacs.conversations.ui.adapter.KnownHostsAdapter;
import eu.siacs.conversations.utils.CryptoHelper;
import eu.siacs.conversations.utils.UIHelper;
+import eu.siacs.conversations.utils.XmppUri;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xmpp.OnKeyStatusUpdated;
import eu.siacs.conversations.xmpp.XmppConnection;
@@ -251,6 +254,7 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
private TableRow mPushRow;
private String mSavedInstanceAccount;
private boolean mSavedInstanceInit = false;
+ private Button mClearDevicesButton;
public void refreshUiReal() {
invalidateOptionsMenu();
@@ -373,13 +377,24 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
}
@Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == REQUEST_BATTERY_OP || requestCode == REQUEST_DATA_SAVER) {
updateAccountInformation(mAccount == null);
}
}
+ @Override
+ protected void processFingerprintVerification(XmppUri uri) {
+ if (mAccount != null && mAccount.getJid().toBareJid().equals(uri.getJid()) && uri.hasFingerprints()) {
+ if (xmppConnectionService.verifyFingerprints(mAccount,uri.getFingerprints())) {
+ Toast.makeText(this,R.string.verified_fingerprints,Toast.LENGTH_SHORT).show();
+ }
+ } else {
+ Toast.makeText(this,R.string.invalid_barcode,Toast.LENGTH_SHORT).show();
+ }
+ }
+
protected void updateSaveButton() {
boolean accountInfoEdited = accountInfoEdited();
@@ -502,6 +517,13 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
this.mNamePort = (LinearLayout) findViewById(R.id.name_port);
this.mHostname = (EditText) findViewById(R.id.hostname);
this.mHostname.addTextChangedListener(mTextWatcher);
+ this.mClearDevicesButton = (Button) findViewById(R.id.clear_devices);
+ this.mClearDevicesButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ showWipePepDialog();
+ }
+ });
this.mPort = (EditText) findViewById(R.id.port);
this.mPort.setText("5222");
this.mPort.addTextChangedListener(mTextWatcher);
@@ -540,12 +562,14 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
final MenuItem showMoreInfo = menu.findItem(R.id.action_server_info_show_more);
final MenuItem changePassword = menu.findItem(R.id.action_change_password_on_server);
final MenuItem showPassword = menu.findItem(R.id.action_show_password);
- final MenuItem clearDevices = menu.findItem(R.id.action_clear_devices);
final MenuItem renewCertificate = menu.findItem(R.id.action_renew_certificate);
final MenuItem mamPrefs = menu.findItem(R.id.action_mam_prefs);
final MenuItem changePresence = menu.findItem(R.id.action_change_presence);
+ final MenuItem share = menu.findItem(R.id.action_share);
renewCertificate.setVisible(mAccount != null && mAccount.getPrivateKeyAlias() != null);
+ share.setVisible(mAccount != null && !mInitMode);
+
if (mAccount != null && mAccount.isOnlineAndConnected()) {
if (!mAccount.getXmppConnection().getFeatures().blocking()) {
showBlocklist.setVisible(false);
@@ -554,17 +578,12 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
changePassword.setVisible(false);
}
mamPrefs.setVisible(mAccount.getXmppConnection().getFeatures().mam());
- Set<Integer> otherDevices = mAccount.getAxolotlService().getOwnDeviceIds();
- if (otherDevices == null || otherDevices.isEmpty() || !Config.supportOmemo()) {
- clearDevices.setVisible(false);
- }
changePresence.setVisible(manuallyChangePresence());
} else {
showQrCode.setVisible(false);
showBlocklist.setVisible(false);
showMoreInfo.setVisible(false);
changePassword.setVisible(false);
- clearDevices.setVisible(false);
mamPrefs.setVisible(false);
changePresence.setVisible(false);
}
@@ -660,6 +679,10 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
this.mPassword.requestFocus();
}
}
+ if (mPendingFingerprintVerificationUri != null) {
+ processFingerprintVerification(mPendingFingerprintVerificationUri);
+ mPendingFingerprintVerificationUri = null;
+ }
updateAccountInformation(init);
}
@@ -700,15 +723,21 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
case R.id.action_server_info_show_more:
changeMoreTableVisibility(!item.isChecked());
break;
+ case R.id.action_share_barcode:
+ shareBarcode();
+ break;
+ case R.id.action_share_http:
+ shareLink(true);
+ break;
+ case R.id.action_share_uri:
+ shareLink(false);
+ break;
case R.id.action_change_password_on_server:
gotoChangePassword(null);
break;
case R.id.action_mam_prefs:
editMamPrefs();
break;
- case R.id.action_clear_devices:
- showWipePepDialog();
- break;
case R.id.action_renew_certificate:
renewCertificate();
break;
@@ -722,6 +751,27 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
return super.onOptionsItemSelected(item);
}
+ private void shareLink(boolean http) {
+ Intent intent = new Intent(Intent.ACTION_SEND);
+ intent.setType("text/plain");
+ String text;
+ if (http) {
+ text = "https://conversations.im/i/"+mAccount.getJid().toBareJid().toString();
+ } else {
+ text = mAccount.getShareableUri();
+ }
+ intent.putExtra(Intent.EXTRA_TEXT,text);
+ startActivity(Intent.createChooser(intent, getText(R.string.share_with)));
+ }
+
+ private void shareBarcode() {
+ Intent intent = new Intent(Intent.ACTION_SEND);
+ intent.putExtra(Intent.EXTRA_STREAM,BarcodeProvider.getUriForAccount(this,mAccount));
+ intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ intent.setType("image/png");
+ startActivity(Intent.createChooser(intent, getText(R.string.share_with)));
+ }
+
private void changeMoreTableVisibility(boolean visible) {
mMoreTable.setVisibility(visible ? View.VISIBLE : View.GONE);
}
@@ -904,15 +954,21 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat
}
boolean hasKeys = false;
keys.removeAllViews();
- for (final String fingerprint : mAccount.getAxolotlService().getFingerprintsForOwnSessions()) {
- if (ownAxolotlFingerprint.equals(fingerprint)) {
- continue;
+ for(XmppAxolotlSession session : mAccount.getAxolotlService().findOwnSessions()) {
+ if (!session.getTrust().isCompromised()) {
+ boolean highlight = session.getFingerprint().equals(messageFingerprint);
+ addFingerprintRow(keys,session,highlight);
+ hasKeys = true;
}
- boolean highlight = fingerprint.equals(messageFingerprint);
- hasKeys |= addFingerprintRow(keys, mAccount, fingerprint, highlight);
}
if (hasKeys && Config.supportOmemo()) {
keysCard.setVisibility(View.VISIBLE);
+ Set<Integer> otherDevices = mAccount.getAxolotlService().getOwnDeviceIds();
+ if (otherDevices == null || otherDevices.isEmpty()) {
+ mClearDevicesButton.setVisibility(View.GONE);
+ } else {
+ mClearDevicesButton.setVisibility(View.VISIBLE);
+ }
} else {
keysCard.setVisibility(View.GONE);
}
diff --git a/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java b/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java
index 7fcb1e3c..bc80f90a 100644
--- a/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/SettingsActivity.java
@@ -7,6 +7,7 @@ import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.content.pm.PackageManager;
+import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.preference.ListPreference;
@@ -14,8 +15,10 @@ import android.preference.Preference;
import android.preference.PreferenceCategory;
import android.preference.PreferenceManager;
import android.preference.PreferenceScreen;
+import android.util.Log;
import android.widget.Toast;
+import java.io.File;
import java.security.KeyStoreException;
import java.util.ArrayList;
import java.util.Arrays;
@@ -35,6 +38,12 @@ import eu.siacs.conversations.xmpp.jid.Jid;
public class SettingsActivity extends XmppActivity implements
OnSharedPreferenceChangeListener {
+ public static final String KEEP_FOREGROUND_SERVICE = "enable_foreground_service";
+ public static final String AWAY_WHEN_SCREEN_IS_OFF = "away_when_screen_off";
+ public static final String TREAT_VIBRATE_AS_SILENT = "treat_vibrate_as_silent";
+ public static final String MANUALLY_CHANGE_PRESENCE = "manually_change_presence";
+ public static final String BLIND_TRUST_BEFORE_VERIFICATION = "btbv";
+
public static final int REQUEST_WRITE_LOGS = 0xbf8701;
private SettingsFragment mSettingsFragment;
@@ -156,6 +165,26 @@ public class SettingsActivity extends XmppActivity implements
}
});
+ if (Config.ONLY_INTERNAL_STORAGE) {
+ final Preference cleanCachePreference = mSettingsFragment.findPreference("clean_cache");
+ cleanCachePreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ cleanCache();
+ return true;
+ }
+ });
+
+ final Preference cleanPrivateStoragePreference = mSettingsFragment.findPreference("clean_private_storage");
+ cleanPrivateStoragePreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
+ @Override
+ public boolean onPreferenceClick(Preference preference) {
+ cleanPrivateStorage();
+ return true;
+ }
+ });
+ }
+
final Preference deleteOmemoPreference = mSettingsFragment.findPreference("delete_omemo_identities");
deleteOmemoPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
@@ -166,6 +195,57 @@ public class SettingsActivity extends XmppActivity implements
});
}
+ private void cleanCache() {
+ Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
+ intent.setData(Uri.parse("package:" + getPackageName()));
+ startActivity(intent);
+ }
+
+ private void cleanPrivateStorage() {
+ cleanPrivatePictures();
+ cleanPrivateFiles();
+ }
+
+ private void cleanPrivatePictures() {
+ try {
+ File dir = new File(getFilesDir().getAbsolutePath(), "/Pictures/");
+ File[] array = dir.listFiles();
+ if (array != null) {
+ for (int b = 0; b < array.length; b++) {
+ String name = array[b].getName().toLowerCase();
+ if (name.equals(".nomedia")) {
+ continue;
+ }
+ if (array[b].isFile()) {
+ array[b].delete();
+ }
+ }
+ }
+ } catch (Throwable e) {
+ Log.e("CleanCache", e.toString());
+ }
+ }
+
+ private void cleanPrivateFiles() {
+ try {
+ File dir = new File(getFilesDir().getAbsolutePath(), "/Files/");
+ File[] array = dir.listFiles();
+ if (array != null) {
+ for (int b = 0; b < array.length; b++) {
+ String name = array[b].getName().toLowerCase();
+ if (name.equals(".nomedia")) {
+ continue;
+ }
+ if (array[b].isFile()) {
+ array[b].delete();
+ }
+ }
+ }
+ } catch (Throwable e) {
+ Log.e("CleanCache", e.toString());
+ }
+ }
+
private void deleteOmemoIdentities() {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.pref_delete_omemo_identities);
@@ -227,10 +307,10 @@ public class SettingsActivity extends XmppActivity implements
final List<String> resendPresence = Arrays.asList(
"confirm_messages",
"xa_on_silent_mode",
- "away_when_screen_off",
+ AWAY_WHEN_SCREEN_IS_OFF,
"allow_message_correction",
- "treat_vibrate_as_silent",
- "manually_change_presence",
+ TREAT_VIBRATE_AS_SILENT,
+ MANUALLY_CHANGE_PRESENCE,
"last_activity");
if (name.equals("resource")) {
String resource = preferences.getString("resource", "mobile")
@@ -248,19 +328,18 @@ public class SettingsActivity extends XmppActivity implements
}
}
}
- } else if (name.equals("keep_foreground_service")) {
- boolean foreground_service = preferences.getBoolean("keep_foreground_service",false);
+ } else if (name.equals(KEEP_FOREGROUND_SERVICE)) {
+ boolean foreground_service = preferences.getBoolean(KEEP_FOREGROUND_SERVICE,false);
if (!foreground_service) {
xmppConnectionService.clearStartTimeCounter();
}
xmppConnectionService.toggleForegroundService();
} else if (resendPresence.contains(name)) {
if (xmppConnectionServiceBound) {
- if (name.equals("away_when_screen_off")
- || name.equals("manually_change_presence")) {
+ if (name.equals(AWAY_WHEN_SCREEN_IS_OFF) || name.equals(MANUALLY_CHANGE_PRESENCE)) {
xmppConnectionService.toggleScreenEventReceiver();
}
- if (name.equals("manually_change_presence") && !noAccountUsesPgp()) {
+ if (name.equals(MANUALLY_CHANGE_PRESENCE) && !noAccountUsesPgp()) {
Toast.makeText(this, R.string.republish_pgp_keys, Toast.LENGTH_LONG).show();
}
xmppConnectionService.refreshAllPresences();
diff --git a/src/main/java/eu/siacs/conversations/ui/SettingsFragment.java b/src/main/java/eu/siacs/conversations/ui/SettingsFragment.java
index e4185abc..b5529bad 100644
--- a/src/main/java/eu/siacs/conversations/ui/SettingsFragment.java
+++ b/src/main/java/eu/siacs/conversations/ui/SettingsFragment.java
@@ -3,6 +3,7 @@ package eu.siacs.conversations.ui;
import android.app.Dialog;
import android.os.Bundle;
import android.preference.Preference;
+import android.preference.PreferenceCategory;
import android.preference.PreferenceFragment;
import android.preference.PreferenceScreen;
import android.view.View;
@@ -11,6 +12,7 @@ import android.view.ViewParent;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
+import eu.siacs.conversations.Config;
import eu.siacs.conversations.R;
public class SettingsFragment extends PreferenceFragment {
@@ -52,6 +54,16 @@ public class SettingsFragment extends PreferenceFragment {
// Load the preferences from an XML resource
addPreferencesFromResource(R.xml.preferences);
+
+ // Remove from standard preferences if the flag ONLY_INTERNAL_STORAGE is not true
+ if (!Config.ONLY_INTERNAL_STORAGE) {
+ PreferenceCategory mCategory = (PreferenceCategory) findPreference("security_options");
+ Preference mPref1 = findPreference("clean_cache");
+ Preference mPref2 = findPreference("clean_private_storage");
+ mCategory.removePreference(mPref1);
+ mCategory.removePreference(mPref2);
+ }
+
}
@Override
diff --git a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java
index 2d24fbbf..25ce50eb 100644
--- a/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/StartConversationActivity.java
@@ -401,7 +401,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU
EnterJidDialog dialog = new EnterJidDialog(
this, mKnownHosts, mActivatedAccounts,
getString(R.string.create_contact), getString(R.string.create),
- prefilledJid, null, !invite.hasFingerprints()
+ prefilledJid, null, invite == null || !invite.hasFingerprints()
);
dialog.setOnEnterJidDialogPositiveListener(new EnterJidDialog.OnEnterJidDialogPositiveListener() {
@@ -420,9 +420,11 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU
if (contact.showInRoster()) {
throw new EnterJidDialog.JidError(getString(R.string.contact_already_exists));
} else {
- //contact.addOtrFingerprint(fingerprint);
xmppConnectionService.createContact(contact);
- switchToConversation(contact);
+ if (invite != null && invite.hasFingerprints()) {
+ xmppConnectionService.verifyFingerprints(contact,invite.getFingerprints());
+ }
+ switchToConversation(contact, invite == null ? null : invite.getBody());
return true;
}
}
@@ -561,11 +563,11 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU
return xmppConnectionService.findAccountByJid(jid);
}
- protected void switchToConversation(Contact contact) {
+ protected void switchToConversation(Contact contact, String body) {
Conversation conversation = xmppConnectionService
.findOrCreateConversation(contact.getAccount(),
contact.getJid(), false);
- switchToConversation(conversation);
+ switchToConversation(conversation, body, false);
}
public static void populateAccountSpinner(Context context, List<String> accounts, Spinner spinner) {
@@ -624,7 +626,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU
showCreateConferenceDialog();
return true;
case R.id.action_scan_qr_code:
- new IntentIntegrator(this).initiateScan();
+ new IntentIntegrator(this).initiateScan(Arrays.asList("AZTEC","QR_CODE"));
return true;
case R.id.action_hide_offline:
mHideOfflineContacts = !item.isChecked();
@@ -843,7 +845,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU
private boolean handleJid(Invite invite) {
Account account = xmppConnectionService.findAccountByJid(invite.getJid());
- if (account != null && invite.hasFingerprints()) {
+ if (account != null && !account.isOptionSet(Account.OPTION_DISABLED) && invite.hasFingerprints()) {
if (xmppConnectionService.verifyFingerprints(account,invite.getFingerprints())) {
switchToAccount(account);
finish();
@@ -854,7 +856,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU
if (invite.isMuc()) {
Conversation muc = xmppConnectionService.findFirstMuc(invite.getJid());
if (muc != null) {
- switchToConversation(muc);
+ switchToConversation(muc,invite.getBody(),false);
return true;
} else {
showJoinConferenceDialog(invite.getJid().toBareJid().toString());
@@ -868,7 +870,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU
if (invite.hasFingerprints()) {
xmppConnectionService.verifyFingerprints(contact,invite.getFingerprints());
}
- switchToConversation(contact);
+ switchToConversation(contact,invite.getBody());
return true;
} else {
if (mMenuSearchView != null) {
@@ -1054,10 +1056,14 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU
activity.conference_context_id = acmi.position;
} else if (mResContextMenu == R.menu.contact_context) {
activity.contact_context_id = acmi.position;
- final Blockable contact = (Contact) activity.contacts.get(acmi.position);
+ final Contact contact = (Contact) activity.contacts.get(acmi.position);
final MenuItem blockUnblockItem = menu.findItem(R.id.context_contact_block_unblock);
+ final MenuItem showContactDetailsItem = menu.findItem(R.id.context_contact_details);
+ if (contact.isSelf()) {
+ showContactDetailsItem.setVisible(false);
+ }
XmppConnection xmpp = contact.getAccount().getXmppConnection();
- if (xmpp != null && xmpp.getFeatures().blocking()) {
+ if (xmpp != null && xmpp.getFeatures().blocking() && !contact.isSelf()) {
if (contact.isBlocked()) {
blockUnblockItem.setTitle(R.string.unblock_contact);
} else {
diff --git a/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java
index 26836395..1f7951e4 100644
--- a/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/TrustKeysActivity.java
@@ -1,7 +1,12 @@
package eu.siacs.conversations.ui;
+import android.app.ActionBar;
import android.content.Intent;
import android.os.Bundle;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.Menu;
+import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
@@ -10,9 +15,13 @@ import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.Toast;
+import com.google.zxing.integration.android.IntentIntegrator;
+import com.google.zxing.integration.android.IntentResult;
+
import org.whispersystems.libaxolotl.IdentityKey;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -23,9 +32,9 @@ import eu.siacs.conversations.OmemoActivity;
import eu.siacs.conversations.R;
import eu.siacs.conversations.crypto.axolotl.AxolotlService;
import eu.siacs.conversations.crypto.axolotl.FingerprintStatus;
-import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Conversation;
+import eu.siacs.conversations.utils.XmppUri;
import eu.siacs.conversations.xmpp.OnKeyStatusUpdated;
import eu.siacs.conversations.xmpp.jid.InvalidJidException;
import eu.siacs.conversations.xmpp.jid.Jid;
@@ -64,6 +73,7 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat
finish();
}
};
+ private Toast mUseCameraHintToast = null;
@Override
protected void refreshUiReal() {
@@ -102,6 +112,61 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat
}
}
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ getMenuInflater().inflate(R.menu.trust_keys, menu);
+ mUseCameraHintToast = Toast.makeText(this,R.string.use_camera_icon_to_scan_barcode,Toast.LENGTH_LONG);
+ ActionBar actionBar = getActionBar();
+ mUseCameraHintToast.setGravity(Gravity.TOP | Gravity.END, 0 ,actionBar == null ? 0 : actionBar.getHeight());
+ mUseCameraHintToast.show();
+ return super.onCreateOptionsMenu(menu);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case R.id.action_scan_qr_code:
+ if (hasPendingKeyFetches()) {
+ Toast.makeText(this, R.string.please_wait_for_keys_to_be_fetched, Toast.LENGTH_SHORT).show();
+ } else {
+ new IntentIntegrator(this).initiateScan(Arrays.asList("AZTEC","QR_CODE"));
+ return true;
+ }
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ protected void onStop() {
+ super.onStop();
+ if (mUseCameraHintToast != null) {
+ mUseCameraHintToast.cancel();
+ }
+ }
+
+ @Override
+ protected void processFingerprintVerification(XmppUri uri) {
+ if (mConversation != null
+ && mAccount != null
+ && uri.hasFingerprints()
+ && mAccount.getAxolotlService().getCryptoTargets(mConversation).contains(uri.getJid())) {
+ boolean performedVerification = xmppConnectionService.verifyFingerprints(mAccount.getRoster().getContact(uri.getJid()),uri.getFingerprints());
+ boolean keys = reloadFingerprints();
+ if (performedVerification && !keys && !hasNoOtherTrustedKeys() && !hasPendingKeyFetches()) {
+ Toast.makeText(this,R.string.all_omemo_keys_have_been_verified, Toast.LENGTH_SHORT).show();
+ finishOk();
+ return;
+ } else if (performedVerification) {
+ Toast.makeText(this,R.string.verified_fingerprints,Toast.LENGTH_SHORT).show();
+ }
+ } else {
+ reloadFingerprints();
+ Log.d(Config.LOGTAG,"xmpp uri was: "+uri.getJid()+" has Fingerprints: "+Boolean.toString(uri.hasFingerprints()));
+ Toast.makeText(this,R.string.barcode_does_not_contain_fingerprints_for_this_conversation,Toast.LENGTH_SHORT).show();
+ }
+ populateView();
+ }
+
private void populateView() {
setTitle(getString(R.string.trust_omemo_fingerprints));
ownKeys.removeAllViews();
@@ -216,8 +281,13 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat
if (this.mAccount != null && intent != null) {
String uuid = intent.getStringExtra("conversation");
this.mConversation = xmppConnectionService.findConversationByUuid(uuid);
- reloadFingerprints();
- populateView();
+ if (this.mPendingFingerprintVerificationUri != null) {
+ processFingerprintVerification(this.mPendingFingerprintVerificationUri);
+ this.mPendingFingerprintVerificationUri = null;
+ } else {
+ reloadFingerprints();
+ populateView();
+ }
}
}
@@ -236,15 +306,22 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat
@Override
public void onKeyStatusUpdated(final AxolotlService.FetchStatus report) {
+ final boolean keysToTrust = reloadFingerprints();
if (report != null) {
lastFetchReport = report;
runOnUiThread(new Runnable() {
@Override
public void run() {
+ if (mUseCameraHintToast != null && !keysToTrust) {
+ mUseCameraHintToast.cancel();
+ }
switch (report) {
case ERROR:
Toast.makeText(TrustKeysActivity.this,R.string.error_fetching_omemo_key,Toast.LENGTH_SHORT).show();
break;
+ case SUCCESS_TRUSTED:
+ Toast.makeText(TrustKeysActivity.this,R.string.blindly_trusted_omemo_keys,Toast.LENGTH_LONG).show();
+ break;
case SUCCESS_VERIFIED:
Toast.makeText(TrustKeysActivity.this,
Config.X509_VERIFICATION ? R.string.verified_omemo_key_with_certificate : R.string.all_omemo_keys_have_been_verified,
@@ -255,7 +332,6 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat
});
}
- boolean keysToTrust = reloadFingerprints();
if (keysToTrust || hasPendingKeyFetches() || hasNoOtherTrustedKeys()) {
refreshUi();
} else {
diff --git a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java
index d961497c..583fab78 100644
--- a/src/main/java/eu/siacs/conversations/ui/XmppActivity.java
+++ b/src/main/java/eu/siacs/conversations/ui/XmppActivity.java
@@ -44,7 +44,6 @@ import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.text.InputType;
import android.util.DisplayMetrics;
-import android.util.Log;
import android.util.Pair;
import android.view.MenuItem;
import android.view.View;
@@ -55,10 +54,8 @@ import android.widget.Toast;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
-import com.google.zxing.WriterException;
+import com.google.zxing.aztec.AztecWriter;
import com.google.zxing.common.BitMatrix;
-import com.google.zxing.qrcode.QRCodeWriter;
-import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import net.java.otr4j.session.SessionID;
@@ -81,6 +78,7 @@ import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.entities.MucOptions;
import eu.siacs.conversations.entities.Presences;
import eu.siacs.conversations.services.AvatarService;
+import eu.siacs.conversations.services.BarcodeProvider;
import eu.siacs.conversations.services.XmppConnectionService;
import eu.siacs.conversations.services.XmppConnectionService.XmppConnectionBinder;
import eu.siacs.conversations.utils.CryptoHelper;
@@ -994,7 +992,7 @@ public abstract class XmppActivity extends Activity {
}
protected boolean manuallyChangePresence() {
- return getPreferences().getBoolean("manually_change_presence", false);
+ return getPreferences().getBoolean(SettingsActivity.MANUALLY_CHANGE_PRESENCE, false);
}
protected void unregisterNdefPushMessageCallback() {
@@ -1061,7 +1059,7 @@ public abstract class XmppActivity extends Activity {
Point size = new Point();
getWindowManager().getDefaultDisplay().getSize(size);
final int width = (size.x < size.y ? size.x : size.y);
- Bitmap bitmap = createQrCodeBitmap(uri, width);
+ Bitmap bitmap = BarcodeProvider.createAztecBitmap(uri, width);
ImageView view = new ImageView(this);
view.setBackgroundColor(Color.WHITE);
view.setImageBitmap(bitmap);
@@ -1071,31 +1069,6 @@ public abstract class XmppActivity extends Activity {
}
}
- protected Bitmap createQrCodeBitmap(String input, int size) {
- Log.d(Config.LOGTAG,"qr code requested size: "+size);
- try {
- final QRCodeWriter QR_CODE_WRITER = new QRCodeWriter();
- final Hashtable<EncodeHintType, Object> hints = new Hashtable<>();
- hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
- final BitMatrix result = QR_CODE_WRITER.encode(input, BarcodeFormat.QR_CODE, size, size, hints);
- final int width = result.getWidth();
- final int height = result.getHeight();
- final int[] pixels = new int[width * height];
- for (int y = 0; y < height; y++) {
- final int offset = y * width;
- for (int x = 0; x < width; x++) {
- pixels[offset + x] = result.get(x, y) ? Color.BLACK : Color.TRANSPARENT;
- }
- }
- final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
- Log.d(Config.LOGTAG,"output size: "+width+"x"+height);
- bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
- return bitmap;
- } catch (final WriterException e) {
- return null;
- }
- }
-
protected Account extractAccount(Intent intent) {
String jid = intent != null ? intent.getStringExtra(EXTRA_ACCOUNT) : null;
try {
diff --git a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java
index 6a392ee1..66c60ca5 100644
--- a/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java
+++ b/src/main/java/eu/siacs/conversations/ui/adapter/MessageAdapter.java
@@ -138,7 +138,7 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
}
}
- private void displayStatus(ViewHolder viewHolder, Message message, int type, boolean darkBackground) {
+ private void displayStatus(ViewHolder viewHolder, Message message, int type, boolean darkBackground, boolean inValidSession) {
String filesize = null;
String info = null;
boolean error = false;
@@ -218,8 +218,8 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
.getAccount().getAxolotlService().getFingerprintTrust(
message.getFingerprint());
- if(status == null || (!status.isTrustedAndActive())) {
- viewHolder.indicator.setColorFilter(activity.getWarningTextColor());
+ if(status == null || (type == SENT ? !status.isTrusted() : (!status.isVerified() && inValidSession))) {
+ viewHolder.indicator.setColorFilter(0xffc64545);
viewHolder.indicator.setAlpha(1.0f);
} else {
viewHolder.indicator.clearColorFilter();
@@ -551,7 +551,8 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
@Override
public View getView(int position, View view, ViewGroup parent) {
final Message message = getItem(position);
- final boolean isInValidSession = message.isValidInSession();
+ final boolean omemoEncryption = message.getEncryption() == Message.ENCRYPTION_AXOLOTL;
+ final boolean isInValidSession = message.isValidInSession() && (!omemoEncryption || message.isTrusted());
final Conversation conversation = message.getConversation();
final Account account = conversation.getAccount();
final int type = getItemViewType(position);
@@ -758,11 +759,15 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
} else {
viewHolder.message_box.setBackgroundResource(R.drawable.message_bubble_received_warning);
viewHolder.encryption.setVisibility(View.VISIBLE);
- viewHolder.encryption.setText(CryptoHelper.encryptionTypeToText(message.getEncryption()));
+ if (omemoEncryption && !message.isTrusted()) {
+ viewHolder.encryption.setText(R.string.not_trusted);
+ } else {
+ viewHolder.encryption.setText(CryptoHelper.encryptionTypeToText(message.getEncryption()));
+ }
}
}
- displayStatus(viewHolder, message, type, darkBackground);
+ displayStatus(viewHolder, message, type, darkBackground, isInValidSession);
return view;
}
@@ -866,12 +871,16 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie
mime = "*/*";
}
Uri uri;
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N || Config.ONLY_INTERNAL_STORAGE) {
try {
- uri = FileProvider.getUriForFile(activity, FileBackend.CONVERSATIONS_FILE_PROVIDER, file);
+ uri = FileBackend.getUriForFile(activity, file);
} catch (IllegalArgumentException e) {
- Toast.makeText(activity,activity.getString(R.string.no_permission_to_access_x,file.getAbsolutePath()), Toast.LENGTH_SHORT).show();
- return;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ Toast.makeText(activity, activity.getString(R.string.no_permission_to_access_x, file.getAbsolutePath()), Toast.LENGTH_SHORT).show();
+ return;
+ } else {
+ uri = Uri.fromFile(file);
+ }
}
openIntent.setDataAndType(uri, mime);
openIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
diff --git a/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java b/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java
index 38ebced1..f1a9d8c4 100644
--- a/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java
+++ b/src/main/java/eu/siacs/conversations/utils/CryptoHelper.java
@@ -206,9 +206,13 @@ public final class CryptoHelper {
}
public static String getAccountFingerprint(Account account) {
+ return getFingerprint(account.getJid().toBareJid().toString());
+ }
+
+ public static String getFingerprint(String value) {
try {
MessageDigest md = MessageDigest.getInstance("SHA-256");
- return bytesToHex(md.digest(account.getJid().toBareJid().toString().getBytes("UTF-8")));
+ return bytesToHex(md.digest(value.getBytes("UTF-8")));
} catch (Exception e) {
return "";
}
diff --git a/src/main/java/eu/siacs/conversations/utils/TLSSocketFactory.java b/src/main/java/eu/siacs/conversations/utils/TLSSocketFactory.java
new file mode 100644
index 00000000..9d8a4111
--- /dev/null
+++ b/src/main/java/eu/siacs/conversations/utils/TLSSocketFactory.java
@@ -0,0 +1,69 @@
+package eu.siacs.conversations.utils;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLSocket;
+import javax.net.ssl.SSLSocketFactory;
+import javax.net.ssl.X509TrustManager;
+
+public class TLSSocketFactory extends SSLSocketFactory {
+
+ private final SSLSocketFactory internalSSLSocketFactory;
+
+ public TLSSocketFactory(X509TrustManager[] trustManager, SecureRandom random) throws KeyManagementException, NoSuchAlgorithmException {
+ SSLContext context = SSLContext.getInstance("TLS");
+ context.init(null, trustManager, random);
+ this.internalSSLSocketFactory = context.getSocketFactory();
+ }
+
+ @Override
+ public String[] getDefaultCipherSuites() {
+ return CryptoHelper.getOrderedCipherSuites(internalSSLSocketFactory.getDefaultCipherSuites());
+ }
+
+ @Override
+ public String[] getSupportedCipherSuites() {
+ return internalSSLSocketFactory.getSupportedCipherSuites();
+ }
+
+ @Override
+ public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
+ return enableTLSOnSocket(internalSSLSocketFactory.createSocket(s, host, port, autoClose));
+ }
+
+ @Override
+ public Socket createSocket(String host, int port) throws IOException {
+ return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port));
+ }
+
+ @Override
+ public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
+ return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port, localHost, localPort));
+ }
+
+ @Override
+ public Socket createSocket(InetAddress host, int port) throws IOException {
+ return enableTLSOnSocket(internalSSLSocketFactory.createSocket(host, port));
+ }
+
+ @Override
+ public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
+ return enableTLSOnSocket(internalSSLSocketFactory.createSocket(address, port, localAddress, localPort));
+ }
+
+ private static Socket enableTLSOnSocket(Socket socket) {
+ if(socket != null && (socket instanceof SSLSocket)) {
+ try {
+ SSLSocketHelper.setSecurity((SSLSocket) socket);
+ } catch (NoSuchAlgorithmException e) {
+ //ignoring
+ }
+ }
+ return socket;
+ }
+} \ No newline at end of file
diff --git a/src/main/java/eu/siacs/conversations/utils/XmppUri.java b/src/main/java/eu/siacs/conversations/utils/XmppUri.java
index 1fc9812a..e16377cf 100644
--- a/src/main/java/eu/siacs/conversations/utils/XmppUri.java
+++ b/src/main/java/eu/siacs/conversations/utils/XmppUri.java
@@ -16,6 +16,7 @@ public class XmppUri {
protected String jid;
protected boolean muc;
protected List<Fingerprint> fingerprints = new ArrayList<>();
+ private String body;
public static final String OMEMO_URI_PARAM = "omemo-sid-";
public static final String OTR_URI_PARAM = "otr-fingerprint";
@@ -55,13 +56,14 @@ public class XmppUri {
muc = segments.size() > 1 && "j".equalsIgnoreCase(segments.get(0));
} else if ("xmpp".equalsIgnoreCase(scheme)) {
// sample: xmpp:foo@bar.com
- muc = "join".equalsIgnoreCase(uri.getQuery());
+ muc = isMuc(uri.getQuery());
if (uri.getAuthority() != null) {
jid = uri.getAuthority();
} else {
jid = uri.getSchemeSpecificPart().split("\\?")[0];
}
this.fingerprints = parseFingerprints(uri.getQuery());
+ this.body = parseBody(uri.getQuery());
} else if ("imto".equalsIgnoreCase(scheme)) {
// sample: imto://xmpp/foo@bar.com
try {
@@ -85,7 +87,7 @@ public class XmppUri {
String[] parts = pair.split("=",2);
if (parts.length == 2) {
String key = parts[0].toLowerCase(Locale.US);
- String value = parts[1];
+ String value = parts[1].toLowerCase(Locale.US);
if (OTR_URI_PARAM.equals(key)) {
fingerprints.add(new Fingerprint(FingerprintType.OTR,value));
}
@@ -102,6 +104,30 @@ public class XmppUri {
return fingerprints;
}
+ protected String parseBody(String query) {
+ for(String pair : query == null ? new String[0] : query.split(";")) {
+ final String[] parts = pair.split("=",2);
+ if (parts.length == 2 && "body".equals(parts[0].toLowerCase(Locale.US))) {
+ try {
+ return URLDecoder.decode(parts[1],"UTF-8");
+ } catch (UnsupportedEncodingException e) {
+ return null;
+ }
+ }
+ }
+ return null;
+ }
+
+ protected boolean isMuc(String query) {
+ for(String pair : query == null ? new String[0] : query.split(";")) {
+ final String[] parts = pair.split("=",2);
+ if (parts.length == 1 && "join".equals(parts[0])) {
+ return true;
+ }
+ }
+ return false;
+ }
+
public Jid getJid() {
try {
return this.jid == null ? null :Jid.fromString(this.jid.toLowerCase());
@@ -110,6 +136,10 @@ public class XmppUri {
}
}
+ public String getBody() {
+ return body;
+ }
+
public List<Fingerprint> getFingerprints() {
return this.fingerprints;
}
diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java
index d6a3b2cb..128e1187 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java
@@ -218,7 +218,10 @@ public class XmppConnection implements Runnable {
mXmppConnectionService = service;
}
- protected void changeStatus(final Account.State nextStatus) {
+ protected synchronized void changeStatus(final Account.State nextStatus) {
+ if (Thread.currentThread().isInterrupted()) {
+ Log.d(Config.LOGTAG,account.getJid().toBareJid()+": not changing status to "+nextStatus+" because thread was interrupted");
+ }
if (account.getStatus() != nextStatus) {
if ((nextStatus == Account.State.OFFLINE)
&& (account.getStatus() != Account.State.CONNECTING)
@@ -262,6 +265,7 @@ public class XmppConnection implements Runnable {
break;
}
try {
+ Socket localSocket;
shouldAuthenticate = needsBinding = !account.isOptionSet(Account.OPTION_REGISTER);
tagReader = new XmlReader(wakeLock);
tagWriter = new TagWriter();
@@ -276,8 +280,15 @@ public class XmppConnection implements Runnable {
destination = account.getHostname();
}
Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": connect to " + destination + " via Tor");
- socket = SocksSocketFactory.createSocketOverTor(destination, account.getPort());
- startXmpp();
+ localSocket = SocksSocketFactory.createSocketOverTor(destination, account.getPort());
+ try {
+ startXmpp(localSocket);
+ } catch (InterruptedException e) {
+ Log.d(Config.LOGTAG,account.getJid().toBareJid()+": thread was interrupted before beginning stream");
+ return;
+ } catch (Exception e) {
+ throw new IOException(e.getMessage());
+ }
} else if (extended && account.getHostname() != null && !account.getHostname().isEmpty()) {
InetSocketAddress address = new InetSocketAddress(account.getHostname(), account.getPort());
@@ -288,33 +299,47 @@ public class XmppConnection implements Runnable {
if (features.encryptionEnabled) {
try {
final TlsFactoryVerifier tlsFactoryVerifier = getTlsFactoryVerifier();
- socket = tlsFactoryVerifier.factory.createSocket();
- socket.connect(address, Config.SOCKET_TIMEOUT * 1000);
- final SSLSession session = ((SSLSocket) socket).getSession();
+ localSocket = tlsFactoryVerifier.factory.createSocket();
+ localSocket.connect(address, Config.SOCKET_TIMEOUT * 1000);
+ final SSLSession session = ((SSLSocket) localSocket).getSession();
if (!tlsFactoryVerifier.verifier.verify(account.getServer().getDomainpart(), session)) {
Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": TLS certificate verification failed");
throw new SecurityException();
}
} catch (KeyManagementException e) {
features.encryptionEnabled = false;
- socket = new Socket();
+ localSocket = new Socket();
}
} else {
- socket = new Socket();
- socket.connect(address, Config.SOCKET_TIMEOUT * 1000);
+ localSocket = new Socket();
+ localSocket.connect(address, Config.SOCKET_TIMEOUT * 1000);
}
} catch (IOException e) {
throw new UnknownHostException();
}
- startXmpp();
+ try {
+ startXmpp(localSocket);
+ } catch (InterruptedException e) {
+ Log.d(Config.LOGTAG,account.getJid().toBareJid()+": thread was interrupted before beginning stream");
+ return;
+ } catch (Exception e) {
+ throw new IOException(e.getMessage());
+ }
} else if (DNSHelper.isIp(account.getServer().toString())) {
- socket = new Socket();
+ localSocket = new Socket();
try {
- socket.connect(new InetSocketAddress(account.getServer().toString(), 5222), Config.SOCKET_TIMEOUT * 1000);
+ localSocket.connect(new InetSocketAddress(account.getServer().toString(), 5222), Config.SOCKET_TIMEOUT * 1000);
} catch (IOException e) {
throw new UnknownHostException();
}
- startXmpp();
+ try {
+ startXmpp(localSocket);
+ } catch (InterruptedException e) {
+ Log.d(Config.LOGTAG,account.getJid().toBareJid()+": thread was interrupted before beginning stream");
+ return;
+ } catch (Exception e) {
+ throw new IOException(e.getMessage());
+ }
} else {
final Bundle result = DNSHelper.getSRVRecord(account.getServer(), mXmppConnectionService);
final ArrayList<Parcelable> values = result.getParcelableArrayList("values");
@@ -350,32 +375,37 @@ public class XmppConnection implements Runnable {
}
if (!features.encryptionEnabled) {
- socket = new Socket();
- socket.connect(addr, Config.SOCKET_TIMEOUT * 1000);
+ localSocket = new Socket();
+ localSocket.connect(addr, Config.SOCKET_TIMEOUT * 1000);
} else {
final TlsFactoryVerifier tlsFactoryVerifier = getTlsFactoryVerifier();
- socket = tlsFactoryVerifier.factory.createSocket();
+ localSocket = tlsFactoryVerifier.factory.createSocket();
- if (socket == null) {
+ if (localSocket == null) {
throw new IOException("could not initialize ssl socket");
}
- SSLSocketHelper.setSecurity((SSLSocket) socket);
- SSLSocketHelper.setSNIHost(tlsFactoryVerifier.factory, (SSLSocket) socket, account.getServer().getDomainpart());
- SSLSocketHelper.setAlpnProtocol(tlsFactoryVerifier.factory, (SSLSocket) socket, "xmpp-client");
+ SSLSocketHelper.setSecurity((SSLSocket) localSocket);
+ SSLSocketHelper.setSNIHost(tlsFactoryVerifier.factory, (SSLSocket) localSocket, account.getServer().getDomainpart());
+ SSLSocketHelper.setAlpnProtocol(tlsFactoryVerifier.factory, (SSLSocket) localSocket, "xmpp-client");
- socket.connect(addr, Config.SOCKET_TIMEOUT * 1000);
+ localSocket.connect(addr, Config.SOCKET_TIMEOUT * 1000);
- if (!tlsFactoryVerifier.verifier.verify(account.getServer().getDomainpart(), ((SSLSocket) socket).getSession())) {
+ if (!tlsFactoryVerifier.verifier.verify(account.getServer().getDomainpart(), ((SSLSocket) localSocket).getSession())) {
Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": TLS certificate verification failed");
throw new SecurityException();
}
}
-
- if (startXmpp())
+ if (startXmpp(localSocket)) {
break; // successfully connected to server that speaks xmpp
+ } else {
+ localSocket.close();
+ }
} catch (final SecurityException e) {
throw e;
+ } catch (InterruptedException e) {
+ Log.d(Config.LOGTAG,account.getJid().toBareJid()+": thread was interrupted before beginning stream");
+ return;
} catch (final Throwable e) {
Log.d(Config.LOGTAG, account.getJid().toBareJid().toString() + ": " + e.getMessage() + "(" + e.getClass().getName() + ")");
if (!iterator.hasNext()) {
@@ -385,8 +415,10 @@ public class XmppConnection implements Runnable {
}
}
processStream();
- } catch (final java.lang.SecurityException e) {
+ } catch (final java.lang.SecurityException e) {
this.changeStatus(Account.State.MISSING_INTERNET_PERMISSION);
+ } catch (final RegistrationNotSupportedException e) {
+ this.changeStatus(Account.State.REGISTRATION_NOT_SUPPORTED);
} catch (final IncompatibleServerException e) {
this.changeStatus(Account.State.INCOMPATIBLE_SERVER);
} catch (final SecurityException e) {
@@ -410,12 +442,16 @@ public class XmppConnection implements Runnable {
this.changeStatus(Account.State.OFFLINE);
this.attempt = Math.max(0, this.attempt - 1);
} finally {
- forceCloseSocket();
- if (wakeLock.isHeld()) {
- try {
- wakeLock.release();
- } catch (final RuntimeException ignored) {
+ if (!Thread.currentThread().isInterrupted()) {
+ forceCloseSocket();
+ if (wakeLock.isHeld()) {
+ try {
+ wakeLock.release();
+ } catch (final RuntimeException ignored) {
+ }
}
+ } else {
+ Log.d(Config.LOGTAG,account.getJid().toBareJid()+": not force closing socket and releasing wake lock because thread was interrupted");
}
}
}
@@ -423,27 +459,18 @@ public class XmppConnection implements Runnable {
/**
* Starts xmpp protocol, call after connecting to socket
* @return true if server returns with valid xmpp, false otherwise
- * @throws IOException Unknown tag on connect
- * @throws XmlPullParserException Bad Xml
- * @throws NoSuchAlgorithmException Other error
*/
- private boolean startXmpp() throws IOException, XmlPullParserException, NoSuchAlgorithmException {
+ private boolean startXmpp(Socket socket) throws Exception {
+ if (Thread.currentThread().isInterrupted()) {
+ throw new InterruptedException();
+ }
+ this.socket = socket;
tagWriter.setOutputStream(socket.getOutputStream());
tagReader.setInputStream(socket.getInputStream());
tagWriter.beginDocument();
sendStartStream();
- Tag nextTag;
- while ((nextTag = tagReader.readTag()) != null) {
- if (nextTag.isStart("stream")) {
- return true;
- } else {
- throw new IOException("unknown tag on connect");
- }
- }
- if (socket.isConnected()) {
- socket.close();
- }
- return false;
+ final Tag tag = tagReader.readTag();
+ return tag != null && tag.isStart("stream");
}
private static class TlsFactoryVerifier {
@@ -812,10 +839,8 @@ public class XmppConnection implements Runnable {
} else {
throw new IncompatibleServerException();
}
- } else if (!this.streamFeatures.hasChild("register")
- && account.isOptionSet(Account.OPTION_REGISTER)) {
- forceCloseSocket();
- changeStatus(Account.State.REGISTRATION_NOT_SUPPORTED);
+ } else if (!this.streamFeatures.hasChild("register") && account.isOptionSet(Account.OPTION_REGISTER)) {
+ throw new RegistrationNotSupportedException();
} else if (this.streamFeatures.hasChild("mechanisms")
&& shouldAuthenticate
&& (features.encryptionEnabled || Config.ALLOW_NON_TLS_CONNECTIONS)) {
@@ -950,7 +975,7 @@ public class XmppConnection implements Runnable {
}
public void resetEverything() {
- resetAttemptCount();
+ resetAttemptCount(true);
resetStreamId();
clearIqCallbacks();
mStanzaQueue.clear();
@@ -1359,30 +1384,6 @@ public class XmppConnection implements Runnable {
}
}
- public void waitForPush() {
- if (tagWriter.isActive()) {
- tagWriter.finish();
- new Thread(new Runnable() {
- @Override
- public void run() {
- try {
- while(!tagWriter.finished()) {
- Thread.sleep(10);
- }
- socket.close();
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+": closed tcp without closing stream");
- changeStatus(Account.State.OFFLINE);
- } catch (IOException | InterruptedException e) {
- Log.d(Config.LOGTAG,account.getJid().toBareJid()+": error while closing socket for waitForPush()");
- }
- }
- }).start();
- } else {
- forceCloseSocket();
- Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": closed tcp without closing stream (no waiting)");
- }
- }
-
private void forceCloseSocket() {
if (socket != null) {
try {
@@ -1526,9 +1527,11 @@ public class XmppConnection implements Runnable {
this.sendPacket(new InactivePacket());
}
- public void resetAttemptCount() {
+ public void resetAttemptCount(boolean resetConnectTime) {
this.attempt = 0;
- this.lastConnect = 0;
+ if (resetConnectTime) {
+ this.lastConnect = 0;
+ }
}
public void setInteractive(boolean interactive) {
@@ -1567,6 +1570,10 @@ public class XmppConnection implements Runnable {
}
+ private class RegistrationNotSupportedException extends IOException {
+
+ }
+
public enum Identity {
FACEBOOK,
SLACK,
diff --git a/src/main/java/eu/siacs/conversations/xmpp/jid/Jid.java b/src/main/java/eu/siacs/conversations/xmpp/jid/Jid.java
index 20f1feb4..f551dd20 100644
--- a/src/main/java/eu/siacs/conversations/xmpp/jid/Jid.java
+++ b/src/main/java/eu/siacs/conversations/xmpp/jid/Jid.java
@@ -21,6 +21,8 @@ public final class Jid {
private final String domainpart;
private final String resourcepart;
+ private static final char[] JID_ESCAPING_CHARS = {' ','"','&','\'','/',':','<','>','@','\\'};
+
// It's much more efficient to store the ful JID as well as the parts instead of figuring them
// all out every time (since some characters are displayed but aren't used for comparisons).
private final String displayjid;
@@ -29,6 +31,18 @@ public final class Jid {
return localpart;
}
+ public String getUnescapedLocalpart() {
+ if (localpart == null || !localpart.contains("\\")) {
+ return localpart;
+ } else {
+ String localpart = this.localpart;
+ for(char c : JID_ESCAPING_CHARS) {
+ localpart = localpart.replace(String.format ("\\%02x", (int)c),String.valueOf(c));
+ }
+ return localpart;
+ }
+ }
+
public String getDomainpart() {
return IDN.toUnicode(domainpart);
}