diff options
143 files changed, 1603 insertions, 1339 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 108845c92..d2e8f3c59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ * view per conversation media files in contact and conference details screens * enable foreground service by default for Android 8 (notification can be disabled by long pressing it) * UI improvements +* support TLSv1.3 (ejabberd ≤ 18.06 is incompatible with openssl 1.1.1 - Update ejabberd or downgrade openssl if you get ›Stream opening error‹) * bug fixes #### Version 2.1.0 diff --git a/build.gradle b/build.gradle index 24a06888d..595ae5648 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { google() } dependencies { - classpath 'com.android.tools.build:gradle:3.1.4' + classpath 'com.android.tools.build:gradle:3.2.0' } } @@ -33,7 +33,7 @@ repositories { } configurations { - standardPushimplementation + standardPushImplementation } ext { @@ -42,11 +42,14 @@ ext { dependencies { implementation project(':libs:android-transcoder') - standardPushimplementation('com.google.firebase:firebase-messaging:15.0.2') { + standardPushImplementation ('com.google.firebase:firebase-messaging:15.0.2') { exclude group: 'com.google.firebase', module: 'firebase-core' } implementation 'org.sufficientlysecure:openpgp-api:10.0' - implementation 'com.theartofdev.edmodo:android-image-cropper:2.7.0' + implementation('com.theartofdev.edmodo:android-image-cropper:2.7.+') { + exclude group: 'com.android.support', module: 'appcompat-v7' + exclude group: 'com.android.support', module: 'exifinterface' + } implementation 'org.bouncycastle:bcmail-jdk15on:1.58' implementation 'org.jitsi:org.otr4j:0.22' implementation 'org.gnu.inet:libidn:1.15' @@ -68,14 +71,14 @@ dependencies { implementation "com.android.support:design:$supportLibVersion" implementation "com.android.support:cardview-v7:$supportLibVersion" implementation 'com.github.bumptech.glide:glide:3.8.0' - implementation 'com.github.chrisbanes:PhotoView:2.0.0' + implementation 'com.davemorrissey.labs:subsampling-scale-image-view:3.10.0' implementation 'com.github.rtoshiro.fullscreenvideoview:fullscreenvideoview:1.1.3' implementation 'pub.devrel:easypermissions:1.2.0' implementation 'com.wefika:flowlayout:0.4.1' implementation 'com.googlecode.ez-vcard:ez-vcard:0.10.3' - implementation 'in.championswimmer:SimpleFingerGestures_Android_Library:1.2' implementation 'rocks.xmpp:xmpp-addr:0.8.0' implementation 'org.hsluv:hsluv:0.2' + implementation 'org.conscrypt:conscrypt-android:1.3.0' } ext { @@ -122,7 +125,10 @@ android { flavorDimensions("distribution") productFlavors { - standardPush + standardPush { + dimension "distribution" + versionNameSuffix "+p" + } standard } if (project.hasProperty('mStoreFile') && @@ -138,11 +144,6 @@ android { } } buildTypes { - debug { - debuggable true - buildTypes.release.signingConfig = null - } - release { debuggable false signingConfig = signingConfigs.release @@ -151,11 +152,22 @@ android { runProguard true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } + + debug { + debuggable true + buildTypes.release.signingConfig = null + minifyEnabled true + shrinkResources true + runProguard true + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } } lintOptions { - disable 'ExtraTranslation', 'MissingTranslation', 'InvalidPackage', 'MissingQuantity', 'AppCompatResource' + disable 'ExtraTranslation', 'MissingTranslation', 'InvalidPackage', 'MissingQuantity', 'AppCompatResource', 'RestrictedApi' + checkReleaseBuilds false } subprojects { diff --git a/src/main/AndroidManifest.xml b/src/main/AndroidManifest.xml index bfdb2ec3d..ce3bfc406 100644 --- a/src/main/AndroidManifest.xml +++ b/src/main/AndroidManifest.xml @@ -104,8 +104,8 @@ android:name=".ui.ConversationsActivity" android:label="@string/app_name" android:launchMode="singleTask" - android:minHeight="300dp" android:minWidth="300dp" + android:minHeight="300dp" android:windowSoftInputMode="stateHidden"></activity> <activity android:name=".ui.ScanActivity" @@ -238,7 +238,7 @@ android:name=".ui.SearchActivity" android:label="@string/search_messages" /> <activity - android:name=".ui.ShowFullscreenMessageActivity" + android:name=".ui.MediaViewerActivity" android:configChanges="orientation|screenSize" android:theme="@style/ConversationsTheme.FullScreen"></activity> <activity diff --git a/src/main/java/de/pixart/messenger/Config.java b/src/main/java/de/pixart/messenger/Config.java index 147bb6873..5181b24b1 100644 --- a/src/main/java/de/pixart/messenger/Config.java +++ b/src/main/java/de/pixart/messenger/Config.java @@ -78,7 +78,7 @@ public final class Config { public static final int CONNECT_DISCO_TIMEOUT = 30; public static final int MINI_GRACE_PERIOD = 750; - public static final boolean XEP_0392 = false; //enables a variant of XEP-0392 that is based on HSLUV + public static final boolean XEP_0392 = true; //enables XEP-0392 v0.6.0 public static final int FILE_SIZE = 1048576; // 1 MiB diff --git a/src/main/java/de/pixart/messenger/crypto/XmppDomainVerifier.java b/src/main/java/de/pixart/messenger/crypto/XmppDomainVerifier.java index 3f3c358a7..80a6086aa 100644 --- a/src/main/java/de/pixart/messenger/crypto/XmppDomainVerifier.java +++ b/src/main/java/de/pixart/messenger/crypto/XmppDomainVerifier.java @@ -32,6 +32,70 @@ public class XmppDomainVerifier implements DomainHostnameVerifier { private static final String SRV_NAME = "1.3.6.1.5.5.7.8.7"; private static final String XMPP_ADDR = "1.3.6.1.5.5.7.8.5"; + private static List<String> getCommonNames(X509Certificate certificate) { + List<String> domains = new ArrayList<>(); + try { + X500Name x500name = new JcaX509CertificateHolder(certificate).getSubject(); + RDN[] rdns = x500name.getRDNs(BCStyle.CN); + for (int i = 0; i < rdns.length; ++i) { + domains.add(IETFUtils.valueToString(x500name.getRDNs(BCStyle.CN)[i].getFirst().getValue())); + } + return domains; + } catch (CertificateEncodingException e) { + return domains; + } + } + + private static Pair<String, String> parseOtherName(byte[] otherName) { + try { + ASN1Primitive asn1Primitive = ASN1Primitive.fromByteArray(otherName); + if (asn1Primitive instanceof DERTaggedObject) { + ASN1Primitive inner = ((DERTaggedObject) asn1Primitive).getObject(); + if (inner instanceof DLSequence) { + DLSequence sequence = (DLSequence) inner; + if (sequence.size() >= 2 && sequence.getObjectAt(1) instanceof DERTaggedObject) { + String oid = sequence.getObjectAt(0).toString(); + ASN1Primitive value = ((DERTaggedObject) sequence.getObjectAt(1)).getObject(); + if (value instanceof DERUTF8String) { + return new Pair<>(oid, ((DERUTF8String) value).getString()); + } else if (value instanceof DERIA5String) { + return new Pair<>(oid, ((DERIA5String) value).getString()); + } + } + } + } + return null; + } catch (IOException e) { + return null; + } + } + + private static boolean matchDomain(String needle, List<String> haystack) { + for (String entry : haystack) { + if (entry.startsWith("*.")) { + int offset = 0; + while (offset < needle.length()) { + int i = needle.indexOf('.', offset); + if (i < 0) { + break; + } + Log.d(LOGTAG, "comparing " + needle.substring(i) + " and " + entry.substring(1)); + if (needle.substring(i).equals(entry.substring(1))) { + Log.d(LOGTAG, "domain " + needle + " matched " + entry); + return true; + } + offset = i + 1; + } + } else { + if (entry.equals(needle)) { + Log.d(LOGTAG, "domain " + needle + " matched " + entry); + return true; + } + } + } + return false; + } + @Override public boolean verify(String domain, String hostname, SSLSession sslSession) { try { @@ -92,63 +156,6 @@ public class XmppDomainVerifier implements DomainHostnameVerifier { } } - private static List<String> getCommonNames(X509Certificate certificate) { - List<String> domains = new ArrayList<>(); - try { - X500Name x500name = new JcaX509CertificateHolder(certificate).getSubject(); - RDN[] rdns = x500name.getRDNs(BCStyle.CN); - for (int i = 0; i < rdns.length; ++i) { - domains.add(IETFUtils.valueToString(x500name.getRDNs(BCStyle.CN)[i].getFirst().getValue())); - } - return domains; - } catch (CertificateEncodingException e) { - return domains; - } - } - - private static Pair<String, String> parseOtherName(byte[] otherName) { - try { - ASN1Primitive asn1Primitive = ASN1Primitive.fromByteArray(otherName); - if (asn1Primitive instanceof DERTaggedObject) { - ASN1Primitive inner = ((DERTaggedObject) asn1Primitive).getObject(); - if (inner instanceof DLSequence) { - DLSequence sequence = (DLSequence) inner; - if (sequence.size() >= 2 && sequence.getObjectAt(1) instanceof DERTaggedObject) { - String oid = sequence.getObjectAt(0).toString(); - ASN1Primitive value = ((DERTaggedObject) sequence.getObjectAt(1)).getObject(); - if (value instanceof DERUTF8String) { - return new Pair<>(oid, ((DERUTF8String) value).getString()); - } else if (value instanceof DERIA5String) { - return new Pair<>(oid, ((DERIA5String) value).getString()); - } - } - } - } - return null; - } catch (IOException e) { - return null; - } - } - - private static boolean matchDomain(String needle, List<String> haystack) { - for (String entry : haystack) { - if (entry.startsWith("*.")) { - int i = needle.indexOf('.'); - Log.d(LOGTAG, "comparing " + needle.substring(i) + " and " + entry.substring(1)); - if (i != -1 && needle.substring(i).equals(entry.substring(1))) { - Log.d(LOGTAG, "domain " + needle + " matched " + entry); - return true; - } - } else { - if (entry.equals(needle)) { - Log.d(LOGTAG, "domain " + needle + " matched " + entry); - return true; - } - } - } - return false; - } - private boolean isSelfSigned(X509Certificate certificate) { try { certificate.verify(certificate.getPublicKey()); @@ -162,4 +169,4 @@ public class XmppDomainVerifier implements DomainHostnameVerifier { public boolean verify(String domain, SSLSession sslSession) { return verify(domain, null, sslSession); } -} +}
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/crypto/axolotl/AxolotlService.java b/src/main/java/de/pixart/messenger/crypto/axolotl/AxolotlService.java index 02062eba1..cc9d85008 100644 --- a/src/main/java/de/pixart/messenger/crypto/axolotl/AxolotlService.java +++ b/src/main/java/de/pixart/messenger/crypto/axolotl/AxolotlService.java @@ -483,9 +483,18 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { publishOwnDeviceId(deviceIds); } } + final Set<Integer> oldSet = this.deviceIds.get(jid); + final boolean changed = oldSet == null || oldSet.hashCode() != hash; this.deviceIds.put(jid, deviceIds); - mXmppConnectionService.updateConversationUi(); //update the lock icon - mXmppConnectionService.keyStatusUpdated(null); + if (changed) { + mXmppConnectionService.updateConversationUi(); //update the lock icon + mXmppConnectionService.keyStatusUpdated(null); + if (me) { + mXmppConnectionService.updateAccountUi(); + } + } else { + Log.d(Config.LOGTAG, "skipped device list update because it hasn't changed"); + } } public void wipeOtherPepDevices() { @@ -516,6 +525,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { if (packet.getType() == IqPacket.TYPE.TIMEOUT) { Log.d(Config.LOGTAG, getLogprefix(account) + "Timeout received while retrieving own Device Ids."); } else { + //TODO consider calling registerDevices only after item-not-found to account for broken PEPs Element item = mXmppConnectionService.getIqParser().getItem(packet); Set<Integer> deviceIds = mXmppConnectionService.getIqParser().deviceIds(item); Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": retrieved own device list: " + deviceIds); @@ -540,7 +550,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { } else { Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": own device " + session.getFingerprint() + " was active " + hours + " hours ago"); } - } + } //TODO print last activation diff } } return devices; @@ -1012,28 +1022,33 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { } if (packet != null) { mXmppConnectionService.sendIqPacket(account, packet, (account, response) -> { - synchronized (fetchDeviceIdsMap) { - List<OnDeviceIdsFetched> callbacks = fetchDeviceIdsMap.remove(jid); - if (response.getType() == IqPacket.TYPE.RESULT) { - fetchDeviceListStatus.put(jid, true); - Element item = mXmppConnectionService.getIqParser().getItem(response); - Set<Integer> deviceIds = mXmppConnectionService.getIqParser().deviceIds(item); - registerDevices(jid, deviceIds); - if (callbacks != null) { - for (OnDeviceIdsFetched c : callbacks) { - c.fetched(jid, deviceIds); - } + if (response.getType() == IqPacket.TYPE.RESULT) { + fetchDeviceListStatus.put(jid, true); + Element item = mXmppConnectionService.getIqParser().getItem(response); + Set<Integer> deviceIds = mXmppConnectionService.getIqParser().deviceIds(item); + registerDevices(jid, deviceIds); + final List<OnDeviceIdsFetched> callbacks; + synchronized (fetchDeviceIdsMap) { + callbacks = fetchDeviceIdsMap.remove(jid); + } + if (callbacks != null) { + for (OnDeviceIdsFetched c : callbacks) { + c.fetched(jid, deviceIds); } + } + } else { + if (response.getType() == IqPacket.TYPE.TIMEOUT) { + fetchDeviceListStatus.remove(jid); } else { - if (response.getType() == IqPacket.TYPE.TIMEOUT) { - fetchDeviceListStatus.remove(jid); - } else { - fetchDeviceListStatus.put(jid, false); - } - if (callbacks != null) { - for (OnDeviceIdsFetched c : callbacks) { - c.fetched(jid, null); - } + fetchDeviceListStatus.put(jid, false); + } + final List<OnDeviceIdsFetched> callbacks; + synchronized (fetchDeviceIdsMap) { + callbacks = fetchDeviceIdsMap.remove(jid); + } + if (callbacks != null) { + for (OnDeviceIdsFetched c : callbacks) { + c.fetched(jid, null); } } } @@ -1148,8 +1163,9 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { Set<SignalProtocolAddress> addresses = new HashSet<>(); for (Jid jid : getCryptoTargets(conversation)) { 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)) { + final Set<Integer> ids = deviceIds.get(jid); + if (ids != null && !ids.isEmpty()) { + for (Integer foreignId : ids) { SignalProtocolAddress address = new SignalProtocolAddress(jid.toString(), foreignId); if (sessions.get(address) == null) { IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey(); @@ -1172,22 +1188,21 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { Log.w(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Have no target devices in PEP!"); } } - if (deviceIds.get(account.getJid().asBareJid()) != null) { - for (Integer ownId : this.deviceIds.get(account.getJid().asBareJid())) { - SignalProtocolAddress address = new SignalProtocolAddress(account.getJid().asBareJid().toString(), ownId); - if (sessions.get(address) == null) { - IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey(); - if (identityKey != null) { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already have session for " + address.toString() + ", adding to cache..."); - XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey); - sessions.put(address, session); + Set<Integer> ownIds = this.deviceIds.get(account.getJid().asBareJid()); + for (Integer ownId : (ownIds != null ? ownIds : new HashSet<Integer>())) { + SignalProtocolAddress address = new SignalProtocolAddress(account.getJid().asBareJid().toString(), ownId); + if (sessions.get(address) == null) { + IdentityKey identityKey = axolotlStore.loadSession(address).getSessionState().getRemoteIdentityKey(); + if (identityKey != null) { + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Already have session for " + address.toString() + ", adding to cache..."); + XmppAxolotlSession session = new XmppAxolotlSession(account, axolotlStore, address, identityKey); + sessions.put(address, session); + } else { + Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Found device " + account.getJid().asBareJid() + ":" + ownId); + if (fetchStatusMap.get(address) != FetchStatus.ERROR) { + addresses.add(address); } else { - Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Found device " + account.getJid().asBareJid() + ":" + ownId); - if (fetchStatusMap.get(address) != FetchStatus.ERROR) { - addresses.add(address); - } else { - Log.d(Config.LOGTAG, getLogprefix(account) + "skipping over " + address + " because it's broken"); - } + Log.d(Config.LOGTAG, getLogprefix(account) + "skipping over " + address + " because it's broken"); } } } @@ -1206,12 +1221,7 @@ public class AxolotlService implements OnAdvancedStreamFeaturesLoaded { } Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": createSessionsIfNeeded() - jids with empty device list: " + jidsWithEmptyDeviceList); if (jidsWithEmptyDeviceList.size() > 0) { - fetchDeviceIds(jidsWithEmptyDeviceList, new OnMultipleDeviceIdFetched() { - @Override - public void fetched() { - createSessionsIfNeededActual(conversation); - } - }); + fetchDeviceIds(jidsWithEmptyDeviceList, () -> createSessionsIfNeededActual(conversation)); return true; } else { return createSessionsIfNeededActual(conversation); diff --git a/src/main/java/de/pixart/messenger/crypto/axolotl/FingerprintStatus.java b/src/main/java/de/pixart/messenger/crypto/axolotl/FingerprintStatus.java index d47df0a0b..cfe8bea4d 100644 --- a/src/main/java/de/pixart/messenger/crypto/axolotl/FingerprintStatus.java +++ b/src/main/java/de/pixart/messenger/crypto/axolotl/FingerprintStatus.java @@ -78,6 +78,10 @@ public class FingerprintStatus implements Comparable<FingerprintStatus> { return status; } + public static FingerprintStatus createActive(Boolean trusted) { + return createActive(trusted != null && trusted); + } + public static FingerprintStatus createActive(boolean trusted) { final FingerprintStatus status = new FingerprintStatus(); status.trust = trusted ? Trust.TRUSTED : Trust.UNTRUSTED; diff --git a/src/main/java/de/pixart/messenger/crypto/axolotl/XmppAxolotlMessage.java b/src/main/java/de/pixart/messenger/crypto/axolotl/XmppAxolotlMessage.java index f9b2539c3..71fbd40db 100644 --- a/src/main/java/de/pixart/messenger/crypto/axolotl/XmppAxolotlMessage.java +++ b/src/main/java/de/pixart/messenger/crypto/axolotl/XmppAxolotlMessage.java @@ -27,12 +27,12 @@ import rocks.xmpp.addr.Jid; public class XmppAxolotlMessage { public static final String CONTAINERTAG = "encrypted"; - public static final String HEADER = "header"; - public static final String SOURCEID = "sid"; - public static final String KEYTAG = "key"; - public static final String REMOTEID = "rid"; - public static final String IVTAG = "iv"; - public static final String PAYLOAD = "payload"; + private static final String HEADER = "header"; + private static final String SOURCEID = "sid"; + private static final String KEYTAG = "key"; + private static final String REMOTEID = "rid"; + private static final String IVTAG = "iv"; + private static final String PAYLOAD = "payload"; private static final String KEYTYPE = "AES"; private static final String CIPHERMODE = "AES/GCM/NoPadding"; @@ -50,7 +50,7 @@ public class XmppAxolotlMessage { private final String plaintext; private final String fingerprint; - public XmppAxolotlPlaintextMessage(String plaintext, String fingerprint) { + XmppAxolotlPlaintextMessage(String plaintext, String fingerprint) { this.plaintext = plaintext; this.fingerprint = fingerprint; } @@ -70,7 +70,7 @@ public class XmppAxolotlMessage { private final byte[] key; private final byte[] iv; - public XmppAxolotlKeyTransportMessage(String fingerprint, byte[] key, byte[] iv) { + XmppAxolotlKeyTransportMessage(String fingerprint, byte[] key, byte[] iv) { this.fingerprint = fingerprint; this.key = key; this.iv = iv; @@ -140,7 +140,7 @@ public class XmppAxolotlMessage { } } - public XmppAxolotlMessage(Jid from, int sourceDeviceId) { + XmppAxolotlMessage(Jid from, int sourceDeviceId) { this.from = from; this.sourceDeviceId = sourceDeviceId; this.keys = new SparseArray<>(); @@ -174,11 +174,11 @@ public class XmppAxolotlMessage { return ciphertext != null; } - public void encrypt(String plaintext) throws CryptoFailedException { + void encrypt(String plaintext) throws CryptoFailedException { try { SecretKey secretKey = new SecretKeySpec(innerKey, KEYTYPE); IvParameterSpec ivSpec = new IvParameterSpec(iv); - Cipher cipher = Compatibility.twentyTwo() ? Cipher.getInstance(CIPHERMODE) : Cipher.getInstance(CIPHERMODE, PROVIDER); + Cipher cipher = Compatibility.twentyEight() ? Cipher.getInstance(CIPHERMODE) : Cipher.getInstance(CIPHERMODE, PROVIDER); cipher.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec); this.ciphertext = cipher.doFinal(Config.OMEMO_PADDING ? getPaddedBytes(plaintext) : plaintext.getBytes()); if (Config.PUT_AUTH_TAG_INTO_KEY && this.ciphertext != null) { @@ -273,7 +273,7 @@ public class XmppAxolotlMessage { return session.processReceiving(encryptedKey); } - public XmppAxolotlKeyTransportMessage getParameters(XmppAxolotlSession session, Integer sourceDeviceId) throws CryptoFailedException { + XmppAxolotlKeyTransportMessage getParameters(XmppAxolotlSession session, Integer sourceDeviceId) throws CryptoFailedException { return new XmppAxolotlKeyTransportMessage(session.getFingerprint(), unpackKey(session, sourceDeviceId), getIV()); } @@ -294,7 +294,7 @@ public class XmppAxolotlMessage { key = newKey; } - Cipher cipher = Compatibility.twentyTwo() ? Cipher.getInstance(CIPHERMODE) : Cipher.getInstance(CIPHERMODE, PROVIDER); + Cipher cipher = Compatibility.twentyEight() ? Cipher.getInstance(CIPHERMODE) : Cipher.getInstance(CIPHERMODE, PROVIDER); SecretKeySpec keySpec = new SecretKeySpec(key, KEYTYPE); IvParameterSpec ivSpec = new IvParameterSpec(iv); diff --git a/src/main/java/de/pixart/messenger/crypto/axolotl/XmppAxolotlSession.java b/src/main/java/de/pixart/messenger/crypto/axolotl/XmppAxolotlSession.java index 09045f032..caa7f9c23 100644 --- a/src/main/java/de/pixart/messenger/crypto/axolotl/XmppAxolotlSession.java +++ b/src/main/java/de/pixart/messenger/crypto/axolotl/XmppAxolotlSession.java @@ -110,6 +110,7 @@ public class XmppAxolotlSession implements Comparable<XmppAxolotlSession> { } if (!status.isActive()) { setTrust(status.toActive()); + //TODO: also (re)add to device list? } } else { throw new CryptoFailedException("not encrypting omemo message from fingerprint "+getFingerprint()+" because it was marked as compromised"); diff --git a/src/main/java/de/pixart/messenger/crypto/sasl/ScramMechanism.java b/src/main/java/de/pixart/messenger/crypto/sasl/ScramMechanism.java index 046b68fda..e1a153ef6 100644 --- a/src/main/java/de/pixart/messenger/crypto/sasl/ScramMechanism.java +++ b/src/main/java/de/pixart/messenger/crypto/sasl/ScramMechanism.java @@ -22,31 +22,19 @@ import de.pixart.messenger.xml.TagWriter; abstract class ScramMechanism extends SaslMechanism { // TODO: When channel binding (SCRAM-SHA1-PLUS) is supported in future, generalize this to indicate support and/or usage. private final static String GS2_HEADER = "n,,"; - private String clientFirstMessageBare; - private final String clientNonce; - private byte[] serverSignature = null; - static HMac HMAC; - static Digest DIGEST; private static final byte[] CLIENT_KEY_BYTES = "Client Key".getBytes(); private static final byte[] SERVER_KEY_BYTES = "Server Key".getBytes(); - - private static class KeyPair { - final byte[] clientKey; - final byte[] serverKey; - - KeyPair(final byte[] clientKey, final byte[] serverKey) { - this.clientKey = clientKey; - this.serverKey = serverKey; - } - } + private static final LruCache<String, KeyPair> CACHE; + static HMac HMAC; + static Digest DIGEST; static { CACHE = new LruCache<String, KeyPair>(10) { protected KeyPair create(final String k) { - // Map keys are "bytesToHex(JID),bytesToHex(password),bytesToHex(salt),iterations". + // Map keys are "bytesToHex(JID),bytesToHex(password),bytesToHex(salt),iterations,SASL-Mechanism". // Changing any of these values forces a cache miss. `CryptoHelper.bytesToHex()' // is applied to prevent commas in the strings breaking things. - final String[] kparts = k.split(",", 4); + final String[] kparts = k.split(",", 5); try { final byte[] saltedPassword, serverKey, clientKey; saltedPassword = hi(CryptoHelper.hexToString(kparts[1]).getBytes(), @@ -62,9 +50,10 @@ abstract class ScramMechanism extends SaslMechanism { }; } - private static final LruCache<String, KeyPair> CACHE; - + private final String clientNonce; protected State state = State.INITIAL; + private String clientFirstMessageBare; + private byte[] serverSignature = null; ScramMechanism(final TagWriter tagWriter, final Account account, final SecureRandom rng) { super(tagWriter, account, rng); @@ -74,6 +63,41 @@ abstract class ScramMechanism extends SaslMechanism { clientFirstMessageBare = ""; } + private static synchronized byte[] hmac(final byte[] key, final byte[] input) + throws InvalidKeyException { + HMAC.init(new KeyParameter(key)); + HMAC.update(input, 0, input.length); + final byte[] out = new byte[HMAC.getMacSize()]; + HMAC.doFinal(out, 0); + return out; + } + + public static synchronized byte[] digest(byte[] bytes) { + DIGEST.reset(); + DIGEST.update(bytes, 0, bytes.length); + final byte[] out = new byte[DIGEST.getDigestSize()]; + DIGEST.doFinal(out, 0); + return out; + } + + /* + * Hi() is, essentially, PBKDF2 [RFC2898] with HMAC() as the + * pseudorandom function (PRF) and with dkLen == output length of + * HMAC() == output length of H(). + */ + private static synchronized byte[] hi(final byte[] key, final byte[] salt, final int iterations) + throws InvalidKeyException { + byte[] u = hmac(key, CryptoHelper.concatenateByteArrays(salt, CryptoHelper.ONE)); + byte[] out = u.clone(); + for (int i = 1; i < iterations; i++) { + u = hmac(key, u); + for (int j = 0; j < u.length; j++) { + out[j] ^= u[j]; + } + } + return out; + } + @Override public String getClientFirstMessage() { if (clientFirstMessageBare.isEmpty() && state == State.INITIAL) { @@ -120,13 +144,13 @@ abstract class ScramMechanism extends SaslMechanism { nonce = token.substring(2); break; case 'm': - /* - * RFC 5802: - * m: This attribute is reserved for future extensibility. In this - * version of SCRAM, its presence in a client or a server message - * MUST cause authentication failure when the attribute is parsed by - * the other end. - */ + /* + * RFC 5802: + * m: This attribute is reserved for future extensibility. In this + * version of SCRAM, its presence in a client or a server message + * MUST cause authentication failure when the attribute is parsed by + * the other end. + */ throw new AuthenticationException("Server sent reserved token: `m'"); } } @@ -147,12 +171,13 @@ abstract class ScramMechanism extends SaslMechanism { final byte[] authMessage = (clientFirstMessageBare + ',' + new String(serverFirstMessage) + ',' + clientFinalMessageWithoutProof).getBytes(); - // Map keys are "bytesToHex(JID),bytesToHex(password),bytesToHex(salt),iterations". + // Map keys are "bytesToHex(JID),bytesToHex(password),bytesToHex(salt),iterations,SASL-Mechanism". final KeyPair keys = CACHE.get( CryptoHelper.bytesToHex(account.getJid().asBareJid().toString().getBytes()) + "," + CryptoHelper.bytesToHex(account.getPassword().getBytes()) + "," + CryptoHelper.bytesToHex(salt.getBytes()) + "," - + String.valueOf(iterationCount) + + String.valueOf(iterationCount) + "," + + getMechanism() ); if (keys == null) { throw new AuthenticationException("Invalid keys generated"); @@ -188,7 +213,7 @@ abstract class ScramMechanism extends SaslMechanism { } state = State.VALID_SERVER_RESPONSE; return ""; - } catch(Exception e) { + } catch (Exception e) { throw new AuthenticationException("Server final message does not match calculated final message"); } default: @@ -196,38 +221,13 @@ abstract class ScramMechanism extends SaslMechanism { } } - private static synchronized byte[] hmac(final byte[] key, final byte[] input) - throws InvalidKeyException { - HMAC.init(new KeyParameter(key)); - HMAC.update(input, 0, input.length); - final byte[] out = new byte[HMAC.getMacSize()]; - HMAC.doFinal(out, 0); - return out; - } - - public static synchronized byte[] digest(byte[] bytes) { - DIGEST.reset(); - DIGEST.update(bytes, 0, bytes.length); - final byte[] out = new byte[DIGEST.getDigestSize()]; - DIGEST.doFinal(out, 0); - return out; - } + private static class KeyPair { + final byte[] clientKey; + final byte[] serverKey; - /* - * Hi() is, essentially, PBKDF2 [RFC2898] with HMAC() as the - * pseudorandom function (PRF) and with dkLen == output length of - * HMAC() == output length of H(). - */ - private static synchronized byte[] hi(final byte[] key, final byte[] salt, final int iterations) - throws InvalidKeyException { - byte[] u = hmac(key, CryptoHelper.concatenateByteArrays(salt, CryptoHelper.ONE)); - byte[] out = u.clone(); - for (int i = 1; i < iterations; i++) { - u = hmac(key, u); - for (int j = 0; j < u.length; j++) { - out[j] ^= u[j]; - } + KeyPair(final byte[] clientKey, final byte[] serverKey) { + this.clientKey = clientKey; + this.serverKey = serverKey; } - return out; } }
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/entities/Account.java b/src/main/java/de/pixart/messenger/entities/Account.java index 6fd4c1c05..9e3711535 100644 --- a/src/main/java/de/pixart/messenger/entities/Account.java +++ b/src/main/java/de/pixart/messenger/entities/Account.java @@ -59,197 +59,34 @@ public class Account extends AbstractEntity { public static final int OPTION_USECOMPRESSION = 3; public static final int OPTION_MAGIC_CREATE = 4; public static final int OPTION_REQUIRES_ACCESS_MODE_CHANGE = 5; - public static final int OPTION_LOGGED_IN_SUCCESSFULLY = 6; + public static final int OPTION_LOGGED_IN_SUCCESSFULLY = 6; public static final int OPTION_HTTP_UPLOAD_AVAILABLE = 7; + private static final String KEY_PGP_SIGNATURE = "pgp_signature"; + private static final String KEY_PGP_ID = "pgp_id"; public final HashSet<Pair<String, String>> inProgressDiscoFetches = new HashSet<>(); - - public boolean httpUploadAvailable(long filesize) { - return xmppConnection != null && (xmppConnection.getFeatures().httpUpload(filesize) || xmppConnection.getFeatures().p1S3FileTransfer()); - } - - public boolean httpUploadAvailable() { - return isOptionSet(OPTION_HTTP_UPLOAD_AVAILABLE) || httpUploadAvailable(0); - } - - public void setDisplayName(String displayName) { - this.displayName = displayName; - } - - public String getDisplayName() { - return displayName; - } - - public XmppConnection.Identity getServerIdentity() { - if (xmppConnection == null) { - return XmppConnection.Identity.UNKNOWN; - } else { - return xmppConnection.getServerIdentity(); - } - } - - public Contact getSelfContact() { - return getRoster().getContact(jid); - } - - public boolean hasPendingPgpIntent(Conversation conversation) { - return pgpDecryptionService != null && pgpDecryptionService.hasPendingIntent(conversation); - } - - public boolean isPgpDecryptionServiceConnected() { - return pgpDecryptionService != null && pgpDecryptionService.isConnected(); - } - - public boolean setShowErrorNotification(boolean newValue) { - boolean oldValue = showErrorNotification(); - setKey("show_error", Boolean.toString(newValue)); - return newValue != oldValue; - } - - public boolean showErrorNotification() { - String key = getKey("show_error"); - return key == null || Boolean.parseBoolean(key); - } - - public boolean isEnabled() { - return !isOptionSet(Account.OPTION_DISABLED); - } - - public enum State { - DISABLED(false, false), - OFFLINE(false), - CONNECTING(false), - ONLINE(false), - NO_INTERNET(false), - UNAUTHORIZED, - SERVER_NOT_FOUND, - REGISTRATION_SUCCESSFUL(false), - REGISTRATION_FAILED(true, false), - REGISTRATION_WEB(true, false), - REGISTRATION_CONFLICT(true, false), - REGISTRATION_NOT_SUPPORTED(true, false), - REGISTRATION_PLEASE_WAIT(true, false), - REGISTRATION_PASSWORD_TOO_WEAK(true, false), - TLS_ERROR, - INCOMPATIBLE_SERVER, - TOR_NOT_AVAILABLE, - DOWNGRADE_ATTACK, - SESSION_FAILURE, - BIND_FAILURE, - HOST_UNKNOWN, - STREAM_ERROR, - POLICY_VIOLATION, - PAYMENT_REQUIRED, - MISSING_INTERNET_PERMISSION(false); - - private final boolean isError; - private final boolean attemptReconnect; - - public boolean isError() { - return this.isError; - } - - public boolean isAttemptReconnect() { - return this.attemptReconnect; - } - - State(final boolean isError) { - this(isError, true); - } - - State(final boolean isError, final boolean reconnect) { - this.isError = isError; - this.attemptReconnect = reconnect; - } - - State() { - this(true, true); - } - - public int getReadableId() { - switch (this) { - case DISABLED: - return R.string.account_status_disabled; - case ONLINE: - return R.string.account_status_online; - case CONNECTING: - return R.string.account_status_connecting; - case OFFLINE: - return R.string.account_status_offline; - case UNAUTHORIZED: - return R.string.account_status_unauthorized; - case SERVER_NOT_FOUND: - return R.string.account_status_not_found; - case NO_INTERNET: - return R.string.account_status_no_internet; - case REGISTRATION_FAILED: - return R.string.account_status_regis_fail; - case REGISTRATION_WEB: - return R.string.account_status_regis_web; - case REGISTRATION_CONFLICT: - return R.string.account_status_regis_conflict; - case REGISTRATION_SUCCESSFUL: - return R.string.account_status_regis_success; - case REGISTRATION_NOT_SUPPORTED: - return R.string.account_status_regis_not_sup; - case TLS_ERROR: - return R.string.account_status_tls_error; - case INCOMPATIBLE_SERVER: - return R.string.account_status_incompatible_server; - case TOR_NOT_AVAILABLE: - return R.string.account_status_tor_unavailable; - case BIND_FAILURE: - return R.string.account_status_bind_failure; - case SESSION_FAILURE: - return R.string.session_failure; - case DOWNGRADE_ATTACK: - return R.string.sasl_downgrade; - case HOST_UNKNOWN: - return R.string.account_status_host_unknown; - case POLICY_VIOLATION: - return R.string.account_status_policy_violation; - case REGISTRATION_PLEASE_WAIT: - return R.string.registration_please_wait; - case REGISTRATION_PASSWORD_TOO_WEAK: - return R.string.registration_password_too_weak; - case STREAM_ERROR: - return R.string.account_status_stream_error; - case PAYMENT_REQUIRED: - return R.string.payment_required; - case MISSING_INTERNET_PERMISSION: - return R.string.missing_internet_permission; - default: - return R.string.account_status_unknown; - } - } - } - + protected final JSONObject keys; + private final Roster roster = new Roster(this); + private final Collection<Jid> blocklist = new CopyOnWriteArraySet<>(); public List<Conversation> pendingConferenceJoins = new CopyOnWriteArrayList<>(); public List<Conversation> pendingConferenceLeaves = new CopyOnWriteArrayList<>(); - - private static final String KEY_PGP_SIGNATURE = "pgp_signature"; - private static final String KEY_PGP_ID = "pgp_id"; - protected Jid jid; protected String password; protected int options = 0; - private String rosterVersion; protected State status = State.OFFLINE; - protected final JSONObject keys; protected String resource; protected String avatar; - protected String displayName = null; protected String hostname = null; protected int port = 5222; protected boolean online = false; private OtrService mOtrService = null; + private String rosterVersion; + private String displayName = null; private AxolotlService axolotlService = null; private PgpDecryptionService pgpDecryptionService = null; private XmppConnection xmppConnection = null; private long mEndGracePeriod = 0L; private String otrFingerprint; - private final Roster roster = new Roster(this); private List<Bookmark> bookmarks = new CopyOnWriteArrayList<>(); - private final Collection<Jid> blocklist = new CopyOnWriteArraySet<>(); private Presence.Status presenceStatus = Presence.Status.ONLINE; private String presenceStatusMessage = null; @@ -308,6 +145,57 @@ public class Account extends AbstractEntity { cursor.getString(cursor.getColumnIndex(STATUS_MESSAGE))); } + public boolean httpUploadAvailable(long filesize) { + return xmppConnection != null && (xmppConnection.getFeatures().httpUpload(filesize) || xmppConnection.getFeatures().p1S3FileTransfer()); + } + + public boolean httpUploadAvailable() { + return isOptionSet(OPTION_HTTP_UPLOAD_AVAILABLE) || httpUploadAvailable(0); + } + + public String getDisplayName() { + return displayName; + } + + public void setDisplayName(String displayName) { + this.displayName = displayName; + } + + public XmppConnection.Identity getServerIdentity() { + if (xmppConnection == null) { + return XmppConnection.Identity.UNKNOWN; + } else { + return xmppConnection.getServerIdentity(); + } + } + + public Contact getSelfContact() { + return getRoster().getContact(jid); + } + + public boolean hasPendingPgpIntent(Conversation conversation) { + return pgpDecryptionService != null && pgpDecryptionService.hasPendingIntent(conversation); + } + + public boolean isPgpDecryptionServiceConnected() { + return pgpDecryptionService != null && pgpDecryptionService.isConnected(); + } + + public boolean setShowErrorNotification(boolean newValue) { + boolean oldValue = showErrorNotification(); + setKey("show_error", Boolean.toString(newValue)); + return newValue != oldValue; + } + + public boolean showErrorNotification() { + String key = getKey("show_error"); + return key == null || Boolean.parseBoolean(key); + } + + public boolean isEnabled() { + return !isOptionSet(Account.OPTION_DISABLED); + } + public boolean isOptionSet(final int option) { return ((options & (1 << option)) != 0); } @@ -354,27 +242,27 @@ public class Account extends AbstractEntity { this.password = password; } - public void setHostname(String hostname) { - this.hostname = hostname; - } - public String getHostname() { return this.hostname == null ? "" : this.hostname; } + public void setHostname(String hostname) { + this.hostname = hostname; + } + public boolean isOnion() { final String server = getServer(); return server != null && server.endsWith(".onion"); } - public void setPort(int port) { - this.port = port; - } - public int getPort() { return this.port; } + public void setPort(int port) { + this.port = port; + } + public State getStatus() { if (isOptionSet(OPTION_DISABLED)) { return State.DISABLED; @@ -383,14 +271,14 @@ public class Account extends AbstractEntity { } } - public State getTrueStatus() { - return this.status; - } - public void setStatus(final State status) { this.status = status; } + public State getTrueStatus() { + return this.status; + } + public boolean errorStatus() { return getStatus().isError(); } @@ -401,22 +289,22 @@ public class Account extends AbstractEntity { && getXmppConnection().getAttempt() >= 3; } - public void setPresenceStatus(Presence.Status status) { - this.presenceStatus = status; - } - public Presence.Status getPresenceStatus() { return this.presenceStatus; } - public void setPresenceStatusMessage(String message) { - this.presenceStatusMessage = message; + public void setPresenceStatus(Presence.Status status) { + this.presenceStatus = status; } public String getPresenceStatusMessage() { return this.presenceStatusMessage; } + public void setPresenceStatusMessage(String message) { + this.presenceStatusMessage = message; + } + public String getResource() { return jid.getResource(); } @@ -613,7 +501,7 @@ public class Account extends AbstractEntity { return getBookmark(conferenceJid) != null; } - public Bookmark getBookmark(final Jid jid) { + Bookmark getBookmark(final Jid jid) { for (final Bookmark bookmark : this.bookmarks) { if (bookmark.getJid() != null && jid.asBareJid().equals(bookmark.getJid().asBareJid())) { return bookmark; @@ -653,16 +541,17 @@ public class Account extends AbstractEntity { List<XmppUri.Fingerprint> fingerprints = this.getFingerprints(); String uri = "xmpp:" + this.getJid().asBareJid().toEscapedString(); if (fingerprints.size() > 0) { - return XmppUri.getFingerprintUri(uri,fingerprints,';'); + return XmppUri.getFingerprintUri(uri, fingerprints, ';'); } else { return uri; } } + public String getShareableLink() { List<XmppUri.Fingerprint> fingerprints = this.getFingerprints(); String uri = Config.inviteUserURL + XmppUri.lameUrlEncode(this.getJid().asBareJid().toEscapedString()); if (fingerprints.size() > 0) { - return XmppUri.getFingerprintUri(uri,fingerprints,'&'); + return XmppUri.getFingerprintUri(uri, fingerprints, '&'); } else { return uri; } @@ -706,4 +595,116 @@ public class Account extends AbstractEntity { public boolean isOnlineAndConnected() { return this.getStatus() == State.ONLINE && this.getXmppConnection() != null; } -} + + public enum State { + DISABLED(false, false), + OFFLINE(false), + CONNECTING(false), + ONLINE(false), + NO_INTERNET(false), + UNAUTHORIZED, + SERVER_NOT_FOUND, + REGISTRATION_SUCCESSFUL(false), + REGISTRATION_FAILED(true, false), + REGISTRATION_WEB(true, false), + REGISTRATION_CONFLICT(true, false), + REGISTRATION_NOT_SUPPORTED(true, false), + REGISTRATION_PLEASE_WAIT(true, false), + REGISTRATION_PASSWORD_TOO_WEAK(true, false), + TLS_ERROR, + INCOMPATIBLE_SERVER, + TOR_NOT_AVAILABLE, + DOWNGRADE_ATTACK, + SESSION_FAILURE, + BIND_FAILURE, + HOST_UNKNOWN, + STREAM_ERROR, + STREAM_OPENING_ERROR, + POLICY_VIOLATION, + PAYMENT_REQUIRED, + MISSING_INTERNET_PERMISSION(false); + + private final boolean isError; + private final boolean attemptReconnect; + + State(final boolean isError) { + this(isError, true); + } + + State(final boolean isError, final boolean reconnect) { + this.isError = isError; + this.attemptReconnect = reconnect; + } + + State() { + this(true, true); + } + + public boolean isError() { + return this.isError; + } + + public boolean isAttemptReconnect() { + return this.attemptReconnect; + } + + public int getReadableId() { + switch (this) { + case DISABLED: + return R.string.account_status_disabled; + case ONLINE: + return R.string.account_status_online; + case CONNECTING: + return R.string.account_status_connecting; + case OFFLINE: + return R.string.account_status_offline; + case UNAUTHORIZED: + return R.string.account_status_unauthorized; + case SERVER_NOT_FOUND: + return R.string.account_status_not_found; + case NO_INTERNET: + return R.string.account_status_no_internet; + case REGISTRATION_FAILED: + return R.string.account_status_regis_fail; + case REGISTRATION_WEB: + return R.string.account_status_regis_web; + case REGISTRATION_CONFLICT: + return R.string.account_status_regis_conflict; + case REGISTRATION_SUCCESSFUL: + return R.string.account_status_regis_success; + case REGISTRATION_NOT_SUPPORTED: + return R.string.account_status_regis_not_sup; + case TLS_ERROR: + return R.string.account_status_tls_error; + case INCOMPATIBLE_SERVER: + return R.string.account_status_incompatible_server; + case TOR_NOT_AVAILABLE: + return R.string.account_status_tor_unavailable; + case BIND_FAILURE: + return R.string.account_status_bind_failure; + case SESSION_FAILURE: + return R.string.session_failure; + case DOWNGRADE_ATTACK: + return R.string.sasl_downgrade; + case HOST_UNKNOWN: + return R.string.account_status_host_unknown; + case POLICY_VIOLATION: + return R.string.account_status_policy_violation; + case REGISTRATION_PLEASE_WAIT: + return R.string.registration_please_wait; + case REGISTRATION_PASSWORD_TOO_WEAK: + return R.string.registration_password_too_weak; + case STREAM_ERROR: + return R.string.account_status_stream_error; + case STREAM_OPENING_ERROR: + return R.string.account_status_stream_opening_error; + case PAYMENT_REQUIRED: + return R.string.payment_required; + case MISSING_INTERNET_PERMISSION: + return R.string.missing_internet_permission; + default: + return R.string.account_status_unknown; + } + } + } +}
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/entities/Message.java b/src/main/java/de/pixart/messenger/entities/Message.java index 8193cbb4d..9a529877b 100644 --- a/src/main/java/de/pixart/messenger/entities/Message.java +++ b/src/main/java/de/pixart/messenger/entities/Message.java @@ -72,7 +72,7 @@ public class Message extends AbstractEntity { public static final String READ_BY_MARKERS = "readByMarkers"; public static final String MARKABLE = "markable"; public static final String ME_COMMAND = "/me"; - + public static final String ERROR_MESSAGE_CANCELLED = "eu.siacs.conversations.cancelled"; public boolean markable = false; protected String conversationUuid; @@ -708,6 +708,8 @@ public class Message extends AbstractEntity { extension = MimeUtils.extractRelevantExtension(url); } catch (MalformedURLException e) { return null; + } catch (Exception e) { + return null; } } return MimeUtils.guessMimeTypeFromExtension(extension); diff --git a/src/main/java/de/pixart/messenger/entities/MucOptions.java b/src/main/java/de/pixart/messenger/entities/MucOptions.java index cf09e73a7..ec7770d77 100644 --- a/src/main/java/de/pixart/messenger/entities/MucOptions.java +++ b/src/main/java/de/pixart/messenger/entities/MucOptions.java @@ -655,6 +655,7 @@ public class MucOptions { public enum Error { NO_RESPONSE, SERVER_NOT_FOUND, + REMOTE_SERVER_TIMEOUT, NONE, NICK_IN_USE, PASSWORD_REQUIRED, diff --git a/src/main/java/de/pixart/messenger/http/HttpUploadConnection.java b/src/main/java/de/pixart/messenger/http/HttpUploadConnection.java index eee8b3c01..589e46d6c 100644 --- a/src/main/java/de/pixart/messenger/http/HttpUploadConnection.java +++ b/src/main/java/de/pixart/messenger/http/HttpUploadConnection.java @@ -2,10 +2,8 @@ package de.pixart.messenger.http; import android.os.PowerManager; import android.util.Log; -import android.util.Pair; -import java.io.FileNotFoundException; -import java.io.IOException; +import java.io.FileInputStream; import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; @@ -31,7 +29,7 @@ import de.pixart.messenger.utils.WakeLockHelper; public class HttpUploadConnection implements Transferable { - public static final List<String> WHITE_LISTED_HEADERS = Arrays.asList( + static final List<String> WHITE_LISTED_HEADERS = Arrays.asList( "Authorization", "Cookie", "Expires" @@ -42,7 +40,7 @@ public class HttpUploadConnection implements Transferable { private final SlotRequester mSlotRequester; private final Method method; private final boolean mUseTor; - private boolean canceled = false; + private boolean cancelled = false; private boolean delayed = false; private DownloadableFile file; private Message message; @@ -52,8 +50,6 @@ public class HttpUploadConnection implements Transferable { private long transmitted = 0; - private InputStream mFileInputStream; - public HttpUploadConnection(Method method, HttpConnectionManager httpConnectionManager) { this.method = method; this.mHttpConnectionManager = httpConnectionManager; @@ -87,14 +83,13 @@ public class HttpUploadConnection implements Transferable { @Override public void cancel() { - this.canceled = true; + this.cancelled = true; } private void fail(String errorMessage) { mHttpConnectionManager.finishUploadConnection(this); message.setTransferable(null); - mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED, errorMessage); - FileBackend.close(mFileInputStream); + mXmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED, cancelled ? Message.ERROR_MESSAGE_CANCELLED : errorMessage); } public void init(Message message, boolean delay) { @@ -110,7 +105,7 @@ public class HttpUploadConnection implements Transferable { if (Config.ENCRYPT_ON_HTTP_UPLOADED || message.getEncryption() == Message.ENCRYPTION_AXOLOTL || message.getEncryption() == Message.ENCRYPTION_OTR) { - this.key = new byte[48]; // todo: change this to 44 for 12-byte IV instead of 16-byte at some point in future + this.key = new byte[48]; mXmppConnectionService.getRNG().nextBytes(this.key); this.file.setKeyAndIv(this.key); } @@ -119,7 +114,7 @@ public class HttpUploadConnection implements Transferable { if (method == Method.P1_S3) { try { - md5 = Checksum.md5(AbstractConnectionManager.createInputStream(file, true).first); + md5 = Checksum.md5(AbstractConnectionManager.upgrade(file, new FileInputStream(file), true)); } catch (Exception e) { Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": unable to calculate md5()", e); fail(e.getMessage()); @@ -128,22 +123,12 @@ public class HttpUploadConnection implements Transferable { } else { md5 = null; } - - Pair<InputStream, Integer> pair; - try { - pair = AbstractConnectionManager.createInputStream(file, true); - } catch (FileNotFoundException e) { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": could not find file to upload - " + e.getMessage()); - fail(e.getMessage()); - return; - } - this.file.setExpectedSize(pair.second); + this.file.setExpectedSize(file.getSize() + (file.getKey() != null ? 16 : 0)); message.resetFileParams(); - this.mFileInputStream = pair.first; this.mSlotRequester.request(method, account, file, mime, md5, new SlotRequester.OnSlotRequested() { @Override public void success(SlotRequester.Slot slot) { - if (!canceled) { + if (!cancelled) { HttpUploadConnection.this.slot = slot; new Thread(HttpUploadConnection.this::upload).start(); } @@ -160,9 +145,11 @@ public class HttpUploadConnection implements Transferable { private void upload() { OutputStream os = null; + InputStream fileInputStream = null; HttpURLConnection connection = null; PowerManager.WakeLock wakeLock = mHttpConnectionManager.createWakeLock("http_upload_" + message.getUuid()); try { + fileInputStream = new FileInputStream(file); final int expectedFileSize = (int) file.getExpectedSize(); final int readTimeout = (expectedFileSize / 2048) + Config.SOCKET_TIMEOUT; //assuming a minimum transfer speed of 16kbit/s wakeLock.acquire(readTimeout); @@ -189,18 +176,18 @@ public class HttpUploadConnection implements Transferable { connection.setConnectTimeout(Config.SOCKET_TIMEOUT * 1000); connection.setReadTimeout(readTimeout * 1000); connection.connect(); + final InputStream innerInputStream = AbstractConnectionManager.upgrade(file, fileInputStream, true); os = connection.getOutputStream(); transmitted = 0; int count; byte[] buffer = new byte[4096]; - while (((count = mFileInputStream.read(buffer)) != -1) && !canceled) { + while (((count = innerInputStream.read(buffer)) != -1) && !cancelled) { transmitted += count; os.write(buffer, 0, count); mHttpConnectionManager.updateConversationUi(false); } os.flush(); os.close(); - mFileInputStream.close(); int code = connection.getResponseCode(); InputStream is = connection.getErrorStream(); if (is != null) { @@ -235,7 +222,7 @@ public class HttpUploadConnection implements Transferable { Log.d(Config.LOGTAG, "http upload failed " + e.getMessage()); fail(e.getMessage()); } finally { - FileBackend.close(mFileInputStream); + FileBackend.close(fileInputStream); FileBackend.close(os); if (connection != null) { connection.disconnect(); diff --git a/src/main/java/de/pixart/messenger/parser/MessageParser.java b/src/main/java/de/pixart/messenger/parser/MessageParser.java index df85f2f25..dca999069 100644 --- a/src/main/java/de/pixart/messenger/parser/MessageParser.java +++ b/src/main/java/de/pixart/messenger/parser/MessageParser.java @@ -303,7 +303,6 @@ public class MessageParser extends AbstractParser implements OnMessagePacketRece Log.d(Config.LOGTAG, AxolotlService.getLogprefix(account) + "Received PEP device list " + deviceIds + " update from " + from + ", processing... "); AxolotlService axolotlService = account.getAxolotlService(); axolotlService.registerDevices(from, deviceIds); - mXmppConnectionService.updateAccountUi(); } else if (Namespace.BOOKMARKS.equals(node)) { Log.d(Config.LOGTAG, "received bookmarks from " + from); if (account.getJid().asBareJid().equals(from)) { diff --git a/src/main/java/de/pixart/messenger/parser/PresenceParser.java b/src/main/java/de/pixart/messenger/parser/PresenceParser.java index 6a878d27f..710eca717 100644 --- a/src/main/java/de/pixart/messenger/parser/PresenceParser.java +++ b/src/main/java/de/pixart/messenger/parser/PresenceParser.java @@ -184,6 +184,8 @@ public class PresenceParser extends AbstractParser implements mucOptions.setError(MucOptions.Error.MEMBERS_ONLY); } else if (error.hasChild("resource-constraint")) { mucOptions.setError(MucOptions.Error.RESOURCE_CONSTRAINT); + } else if (error.hasChild("remote-server-timeout")) { + mucOptions.setError(MucOptions.Error.REMOTE_SERVER_TIMEOUT); } else if (error.hasChild("gone")) { final String gone = error.findChildContent("gone"); final Jid alternate; diff --git a/src/main/java/de/pixart/messenger/persistance/DatabaseBackend.java b/src/main/java/de/pixart/messenger/persistance/DatabaseBackend.java index 4cc5b7955..dac626718 100644 --- a/src/main/java/de/pixart/messenger/persistance/DatabaseBackend.java +++ b/src/main/java/de/pixart/messenger/persistance/DatabaseBackend.java @@ -663,7 +663,7 @@ public class DatabaseBackend extends SQLiteOpenHelper { SQLiteDatabase db = this.getReadableDatabase(); String where = Resolver.Result.DOMAIN + "=?"; String[] whereArgs = {domain}; - Cursor cursor = db.query(RESOLVER_RESULTS_TABLENAME, null, where, whereArgs, null, null, null); + final Cursor cursor = db.query(RESOLVER_RESULTS_TABLENAME, null, where, whereArgs, null, null, null); Resolver.Result result = null; if (cursor != null) { if (cursor.moveToFirst()) { diff --git a/src/main/java/de/pixart/messenger/persistance/FileBackend.java b/src/main/java/de/pixart/messenger/persistance/FileBackend.java index 8397a7bb8..765766b5f 100644 --- a/src/main/java/de/pixart/messenger/persistance/FileBackend.java +++ b/src/main/java/de/pixart/messenger/persistance/FileBackend.java @@ -111,8 +111,7 @@ public class FileBackend { } } - public boolean deleteFile(Message message) { - File file = getFile(message); + public boolean deleteFile(File file) { if (file.delete()) { updateMediaScanner(file); return true; @@ -121,6 +120,11 @@ public class FileBackend { } } + public boolean deleteFile(Message message) { + File file = getFile(message); + return deleteFile(file); + } + public DownloadableFile getFile(Message message) { return getFile(message, true); } @@ -219,8 +223,8 @@ public class FileBackend { } public static String getConversationsDirectory(final String type) { - if (type.equalsIgnoreCase("null") || type == null) { - return getAppMediaDirectory() + "Pix-Art Messenger" + "/"; + if (type.equalsIgnoreCase("null")) { + return Environment.getExternalStorageDirectory().getAbsolutePath() + "/" + "Pix-Art Messenger" + "/"; } else { return getAppMediaDirectory() + "Pix-Art Messenger" + " " + type + "/"; } @@ -858,6 +862,7 @@ public class FileBackend { return cropCenterSquare(input, size); } } catch (FileNotFoundException | SecurityException e) { + Log.d(Config.LOGTAG, "unable to open file " + image.toString(), e); return null; } finally { close(is); @@ -1097,7 +1102,7 @@ public class FileBackend { drawOverlay(bitmap, R.drawable.play_video, 0.75f); } else { bitmap = cropCenterSquare(attachment.getUri(), size); - if ("image/gif".equals(attachment.getMime())) { + if (bitmap != null && "image/gif".equals(attachment.getMime())) { Bitmap withGifOverlay = bitmap.copy(Bitmap.Config.ARGB_8888, true); drawOverlay(withGifOverlay, R.drawable.play_gif, 1.0f); bitmap.recycle(); diff --git a/src/main/java/de/pixart/messenger/services/AbstractConnectionManager.java b/src/main/java/de/pixart/messenger/services/AbstractConnectionManager.java index 65711ef42..4b2239895 100644 --- a/src/main/java/de/pixart/messenger/services/AbstractConnectionManager.java +++ b/src/main/java/de/pixart/messenger/services/AbstractConnectionManager.java @@ -3,21 +3,26 @@ package de.pixart.messenger.services; import android.content.Context; import android.os.PowerManager; import android.os.SystemClock; -import android.util.Pair; +import android.util.Log; -import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.InputStream; import java.io.OutputStream; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; import java.util.concurrent.atomic.AtomicLong; import javax.crypto.Cipher; import javax.crypto.CipherInputStream; import javax.crypto.CipherOutputStream; +import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; +import de.pixart.messenger.Config; import de.pixart.messenger.R; import de.pixart.messenger.entities.DownloadableFile; import de.pixart.messenger.utils.Compatibility; @@ -34,32 +39,26 @@ public class AbstractConnectionManager { this.mXmppConnectionService = service; } - public static Pair<InputStream, Integer> createInputStream(DownloadableFile file, boolean gcm) throws FileNotFoundException { - FileInputStream is; - int size; - is = new FileInputStream(file); - size = (int) file.getSize(); - if (file.getKey() == null) { - return new Pair<>(is, size); - } - try { + public static InputStream upgrade(DownloadableFile file, InputStream is, boolean gcm) throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, InvalidKeyException, NoSuchPaddingException, NoSuchProviderException { + if (file.getKey() != null && file.getIv() != null) { if (gcm) { - Cipher cipher = Compatibility.twentyTwo() ? Cipher.getInstance(CIPHERMODE) : Cipher.getInstance(CIPHERMODE, PROVIDER); + final Cipher cipher = Compatibility.twentyEight() ? Cipher.getInstance(CIPHERMODE) : Cipher.getInstance(CIPHERMODE, PROVIDER); SecretKeySpec keySpec = new SecretKeySpec(file.getKey(), KEYTYPE); IvParameterSpec ivSpec = new IvParameterSpec(file.getIv()); cipher.init(Cipher.ENCRYPT_MODE, keySpec, ivSpec); - return new Pair<>(new CipherInputStream(is, cipher), cipher.getOutputSize(size)); + return new CipherInputStream(is, cipher); } else { IvParameterSpec ips = new IvParameterSpec(file.getIv()); - Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(file.getKey(), KEYTYPE), ips); - return new Pair<>(new CipherInputStream(is, cipher), (size / 16 + 1) * 16); + return new CipherInputStream(is, cipher); } - } catch (Exception e) { - throw new AssertionError(e); + } else { + return is; } } + public static OutputStream createAppendedOutputStream(DownloadableFile file) { return createOutputStream(file, false, true); } @@ -76,23 +75,25 @@ public class AbstractConnectionManager { return os; } } catch (FileNotFoundException e) { + Log.d(Config.LOGTAG, "unable to create output stream", e); return null; } try { if (gcm) { - Cipher cipher = Compatibility.twentyTwo() ? Cipher.getInstance(CIPHERMODE) : Cipher.getInstance(CIPHERMODE, PROVIDER); + final Cipher cipher = Compatibility.twentyEight() ? Cipher.getInstance(CIPHERMODE) : Cipher.getInstance(CIPHERMODE, PROVIDER); SecretKeySpec keySpec = new SecretKeySpec(file.getKey(), KEYTYPE); IvParameterSpec ivSpec = new IvParameterSpec(file.getIv()); cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec); return new CipherOutputStream(os, cipher); } else { IvParameterSpec ips = new IvParameterSpec(file.getIv()); - Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); + final Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(file.getKey(), KEYTYPE), ips); return new CipherOutputStream(os, cipher); } } catch (Exception e) { - throw new AssertionError(e); + Log.d(Config.LOGTAG, "unable to create cipher output stream", e); + return null; } } diff --git a/src/main/java/de/pixart/messenger/services/NotificationService.java b/src/main/java/de/pixart/messenger/services/NotificationService.java index 4982e9fe2..a2d6f541a 100644 --- a/src/main/java/de/pixart/messenger/services/NotificationService.java +++ b/src/main/java/de/pixart/messenger/services/NotificationService.java @@ -64,18 +64,18 @@ import de.pixart.messenger.xmpp.XmppConnection; public class NotificationService { public static final Object CATCHUP_LOCK = new Object(); - - private static final String CONVERSATIONS_GROUP = "de.pixart.messenger"; - private static final int NOTIFICATION_ID_MULTIPLIER = 1024 * 1024; - public static final int NOTIFICATION_ID = 2 * NOTIFICATION_ID_MULTIPLIER; - public static final int FOREGROUND_NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 4; - public static final int ERROR_NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 6; public static final String MESSAGES_CHANNEL_ID = "messages"; public static final String FOREGROUND_CHANNEL_ID = "foreground"; public static final String BACKUP_CHANNEL_ID = "backup"; public static final String UPDATE_CHANNEL_ID = "appupdate"; public static final String VIDEOCOMPRESSION_CHANNEL_ID = "compression"; public static final String ERROR_CHANNEL_ID = "error"; + private static final String CONVERSATIONS_GROUP = "de.pixart.messenger"; + private static final int NOTIFICATION_ID_MULTIPLIER = 1024 * 1024; + public static final int NOTIFICATION_ID = 2 * NOTIFICATION_ID_MULTIPLIER; + public static final int FOREGROUND_NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 4; + public static final int ERROR_NOTIFICATION_ID = NOTIFICATION_ID_MULTIPLIER * 6; + private static final int LED_COLOR = 0xff0080FF; private final XmppConnectionService mXmppConnectionService; private final LinkedHashMap<String, ArrayList<Message>> notifications = new LinkedHashMap<>(); @@ -107,7 +107,7 @@ public class NotificationService { @RequiresApi(api = Build.VERSION_CODES.O) public void initializeChannels() { final Context c = mXmppConnectionService; - NotificationManager notificationManager = c.getSystemService(NotificationManager.class); + final NotificationManager notificationManager = c.getSystemService(NotificationManager.class); if (notificationManager == null) { return; } @@ -159,7 +159,7 @@ public class NotificationService { .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) .setUsage(AudioAttributes.USAGE_NOTIFICATION_RINGTONE) .build()); - messagesChannel.setLightColor(0xff0080FF); + messagesChannel.setLightColor(LED_COLOR); final int dat = 70; final long[] pattern = {0, 3 * dat, dat, dat}; messagesChannel.setVibrationPattern(pattern); @@ -173,10 +173,21 @@ public class NotificationService { NotificationManager.IMPORTANCE_LOW); silentMessagesChannel.setDescription(c.getString(R.string.silent_messages_channel_description)); silentMessagesChannel.setShowBadge(true); - silentMessagesChannel.setLightColor(0xff0080FF); + silentMessagesChannel.setLightColor(LED_COLOR); silentMessagesChannel.enableLights(true); silentMessagesChannel.setGroup("chats"); notificationManager.createNotificationChannel(silentMessagesChannel); + + final NotificationChannel quietHoursChannel = new NotificationChannel("quiet_hours", + c.getString(R.string.title_pref_quiet_hours), + NotificationManager.IMPORTANCE_LOW); + quietHoursChannel.setShowBadge(true); + quietHoursChannel.setLightColor(LED_COLOR); + quietHoursChannel.enableLights(true); + quietHoursChannel.setGroup("chats"); + quietHoursChannel.enableVibration(false); + quietHoursChannel.setSound(null, null); + notificationManager.createNotificationChannel(quietHoursChannel); } public boolean notify(final Message message) { @@ -184,8 +195,7 @@ public class NotificationService { return message.getStatus() == Message.STATUS_RECEIVED && !conversation.isMuted() && (conversation.alwaysNotify() || wasHighlightedOrPrivate(message)) - && (!conversation.isWithStranger() || notificationsFromStrangers()) - ; + && (!conversation.isWithStranger() || notificationsFromStrangers()); } private boolean notificationsFromStrangers() { @@ -200,6 +210,7 @@ public class NotificationService { final long startTime = preferences.getLong("quiet_hours_start", TimePreference.DEFAULT_VALUE) % Config.MILLISECONDS_IN_DAY; final long endTime = preferences.getLong("quiet_hours_end", TimePreference.DEFAULT_VALUE) % Config.MILLISECONDS_IN_DAY; final long nowTime = Calendar.getInstance().getTimeInMillis() % Config.MILLISECONDS_IN_DAY; + if (endTime < startTime) { return nowTime > startTime || nowTime < endTime; } else { @@ -248,6 +259,7 @@ public class NotificationService { } } } + private List<String> getBacklogConversations(Account account) { final List<String> conversations = new ArrayList<>(); for (Iterator<Map.Entry<Conversation, AtomicInteger>> it = mBacklogMessageCounter.entrySet().iterator(); it.hasNext(); ) { @@ -376,6 +388,7 @@ public class NotificationService { private void updateNotification(final boolean notify, final List<String> conversations, final boolean summaryOnly) { final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(mXmppConnectionService); + final boolean quiteHours = isQuietHours(); final boolean notifyOnlyOneChild = notify && conversations != null && conversations.size() == 1; //if this check is changed to > 0 catchup messages will create one notification per conversation if (notifications.size() == 0) { cancel(NOTIFICATION_ID); @@ -385,24 +398,24 @@ public class NotificationService { } final Builder mBuilder; if (notifications.size() == 1 && Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { - mBuilder = buildSingleConversations(notifications.values().iterator().next(), notify); - modifyForSoundVibrationAndLight(mBuilder, notify, preferences); + mBuilder = buildSingleConversations(notifications.values().iterator().next(), notify, quiteHours); + modifyForSoundVibrationAndLight(mBuilder, notify, quiteHours, preferences); notify(NOTIFICATION_ID, mBuilder.build()); } else { - mBuilder = buildMultipleConversation(notify); + mBuilder = buildMultipleConversation(notify, quiteHours); if (notifyOnlyOneChild) { mBuilder.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_CHILDREN); } - modifyForSoundVibrationAndLight(mBuilder, notify, preferences); + modifyForSoundVibrationAndLight(mBuilder, notify, quiteHours, preferences); if (!summaryOnly) { for (Map.Entry<String, ArrayList<Message>> entry : notifications.entrySet()) { String uuid = entry.getKey(); final boolean notifyThis = notifyOnlyOneChild ? conversations.contains(uuid) : notify; - Builder singleBuilder = buildSingleConversations(entry.getValue(), notifyThis); + Builder singleBuilder = buildSingleConversations(entry.getValue(), notifyThis, quiteHours); if (!notifyOnlyOneChild) { singleBuilder.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY); } - modifyForSoundVibrationAndLight(singleBuilder, notifyThis, preferences); + modifyForSoundVibrationAndLight(singleBuilder, notifyThis, quiteHours, preferences); singleBuilder.setGroup(CONVERSATIONS_GROUP); setNotificationColor(singleBuilder); notify(entry.getKey(), NOTIFICATION_ID, singleBuilder.build()); @@ -413,13 +426,13 @@ public class NotificationService { } } - private void modifyForSoundVibrationAndLight(Builder mBuilder, boolean notify, SharedPreferences preferences) { + private void modifyForSoundVibrationAndLight(Builder mBuilder, boolean notify, boolean quietHours, SharedPreferences preferences) { final Resources resources = mXmppConnectionService.getResources(); final String ringtone = preferences.getString("notification_ringtone", resources.getString(R.string.notification_ringtone)); final boolean vibrate = preferences.getBoolean("vibrate_on_notification", resources.getBoolean(R.bool.vibrate_on_notification)); final boolean led = preferences.getBoolean("led", resources.getBoolean(R.bool.led)); final boolean headsup = preferences.getBoolean("notification_headsup", resources.getBoolean(R.bool.headsup_notifications)); - if (notify && !isQuietHours()) { + if (notify && !quietHours) { if (vibrate) { final int dat = 70; final long[] pattern = {0, 3 * dat, dat, dat}; @@ -441,7 +454,7 @@ public class NotificationService { setNotificationColor(mBuilder); mBuilder.setDefaults(0); if (led) { - mBuilder.setLights(0xff0080FF, 2000, 3000); + mBuilder.setLights(LED_COLOR, 2000, 3000); } } @@ -453,8 +466,8 @@ public class NotificationService { } } - private Builder buildMultipleConversation(final boolean notify) { - final Builder mBuilder = new NotificationCompat.Builder(mXmppConnectionService, notify ? "messages" : "silent_messages"); + private Builder buildMultipleConversation(final boolean notify, final boolean quietHours) { + final Builder mBuilder = new NotificationCompat.Builder(mXmppConnectionService, quietHours ? "quiet_hours" : (notify ? "messages" : "silent_messages")); final NotificationCompat.InboxStyle style = new NotificationCompat.InboxStyle(); style.setBigContentTitle(notifications.size() + " " @@ -500,8 +513,8 @@ public class NotificationService { return mBuilder; } - private Builder buildSingleConversations(final ArrayList<Message> messages, final boolean notify) { - final Builder mBuilder = new NotificationCompat.Builder(mXmppConnectionService, notify ? "messages" : "silent_messages"); + private Builder buildSingleConversations(final ArrayList<Message> messages, final boolean notify, final boolean quietHours) { + final Builder mBuilder = new NotificationCompat.Builder(mXmppConnectionService, quietHours ? "quiet_hours" : (notify ? "messages" : "silent_messages")); if (messages.size() >= 1) { final Conversation conversation = (Conversation) messages.get(0).getConversation(); final UnreadConversation.Builder mUnreadBuilder = new UnreadConversation.Builder(conversation.getName().toString()); @@ -896,7 +909,7 @@ public class NotificationService { } mBuilder.setContentIntent(createOpenConversationsIntent()); mBuilder.setWhen(0); - mBuilder.setPriority(Notification.PRIORITY_LOW); + mBuilder.setPriority(Notification.PRIORITY_MIN); mBuilder.setSmallIcon(R.drawable.ic_link_white_24dp); if (Compatibility.runsTwentySix()) { mBuilder.setChannelId(FOREGROUND_CHANNEL_ID); diff --git a/src/main/java/de/pixart/messenger/services/XmppConnectionService.java b/src/main/java/de/pixart/messenger/services/XmppConnectionService.java index 132ede7b6..f47893e48 100644 --- a/src/main/java/de/pixart/messenger/services/XmppConnectionService.java +++ b/src/main/java/de/pixart/messenger/services/XmppConnectionService.java @@ -47,12 +47,14 @@ import net.java.otr4j.session.SessionID; import net.java.otr4j.session.SessionImpl; import net.java.otr4j.session.SessionStatus; +import org.conscrypt.Conscrypt; import org.openintents.openpgp.IOpenPgpService2; import org.openintents.openpgp.util.OpenPgpApi; import org.openintents.openpgp.util.OpenPgpServiceConnection; import java.net.URL; import java.security.SecureRandom; +import java.security.Security; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.text.SimpleDateFormat; @@ -125,7 +127,6 @@ import de.pixart.messenger.utils.ExceptionHelper; import de.pixart.messenger.utils.MimeUtils; import de.pixart.messenger.utils.Namespace; import de.pixart.messenger.utils.OnPhoneContactsLoadedListener; -import de.pixart.messenger.utils.PRNGFixes; import de.pixart.messenger.utils.PhoneHelper; import de.pixart.messenger.utils.QuickLoader; import de.pixart.messenger.utils.ReplacingSerialSingleThreadExecutor; @@ -490,11 +491,6 @@ public class XmppConnectionService extends Service { } public void attachFileToConversation(final Conversation conversation, final Uri uri, final String type, final UiCallback<Message> callback) { - if (FileBackend.weOwnFile(this, uri)) { - Log.d(Config.LOGTAG, "trying to attach file that belonged to us"); - callback.error(R.string.security_error_invalid_file_access, null); - return; - } final Message message; if (conversation.getNextEncryption() == Message.ENCRYPTION_PGP) { message = new Message(conversation, "", Message.ENCRYPTION_DECRYPTED); @@ -512,11 +508,6 @@ public class XmppConnectionService extends Service { } public void attachImageToConversation(final Conversation conversation, final Uri uri, final UiCallback<Message> callback) { - if (FileBackend.weOwnFile(this, uri)) { - Log.d(Config.LOGTAG, "trying to attach file that belonged to us"); - callback.error(R.string.security_error_invalid_file_access, null); - return; - } final String mimeType = MimeUtils.guessMimeTypeFromUri(this, uri); final String compressPictures = getCompressPicturesPreference(); @@ -1100,7 +1091,11 @@ public class XmppConnectionService extends Service { public void onCreate() { OmemoSetting.load(this); ExceptionHelper.init(getApplicationContext()); - PRNGFixes.apply(); + try { + Security.insertProviderAt(Conscrypt.newProvider(), 1); + } catch (Throwable throwable) { + Log.e(Config.LOGTAG, "unable to initialize security provider", throwable); + } Resolver.init(this); this.mRandom = new SecureRandom(); updateMemorizingTrustmanager(); @@ -1160,7 +1155,7 @@ public class XmppConnectionService extends Service { } this.pm = (PowerManager) getSystemService(Context.POWER_SERVICE); - this.wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "XmppConnectionService"); + this.wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, Config.LOGTAG + ":Service"); toggleForegroundService(); updateUnreadCountBadge(); toggleScreenEventReceiver(); @@ -1875,8 +1870,7 @@ public class XmppConnectionService extends Service { public List<Conversation> findAllConferencesWith(Contact contact) { ArrayList<Conversation> results = new ArrayList<>(); for (final Conversation c : conversations) { - if (c.getMode() == Conversation.MODE_MULTI - && (c.getJid().asBareJid().equals(c.getJid().asBareJid()) || c.getMucOptions().isContactInRoom(contact))) { + if (c.getMode() == Conversation.MODE_MULTI && c.getMucOptions().isContactInRoom(contact)) { results.add(c); } } diff --git a/src/main/java/de/pixart/messenger/ui/BlockContactDialog.java b/src/main/java/de/pixart/messenger/ui/BlockContactDialog.java index f7711ce34..4f768b009 100644 --- a/src/main/java/de/pixart/messenger/ui/BlockContactDialog.java +++ b/src/main/java/de/pixart/messenger/ui/BlockContactDialog.java @@ -1,10 +1,8 @@ package de.pixart.messenger.ui; import android.databinding.DataBindingUtil; +import android.support.annotation.StringRes; import android.support.v7.app.AlertDialog; -import android.text.SpannableString; -import android.text.Spanned; -import android.text.style.TypefaceSpan; import android.view.View; import android.widget.Toast; @@ -12,6 +10,7 @@ import de.pixart.messenger.R; import de.pixart.messenger.databinding.DialogBlockContactBinding; import de.pixart.messenger.entities.Blockable; import de.pixart.messenger.entities.Conversation; +import de.pixart.messenger.ui.util.JidDialog; import rocks.xmpp.addr.Jid; public final class BlockContactDialog { @@ -24,23 +23,19 @@ public final class BlockContactDialog { binding.reportSpam.setVisibility(!isBlocked && reporting ? View.VISIBLE : View.GONE); builder.setView(binding.getRoot()); - String value; - SpannableString spannable; + final String value; + @StringRes int res; if (blockable.getJid().getLocal() == null || blockable.getAccount().isBlocked(Jid.ofDomain(blockable.getJid().getDomain()))) { builder.setTitle(isBlocked ? R.string.action_unblock_domain : R.string.action_block_domain); value = Jid.ofDomain(blockable.getJid().getDomain()).toString(); - spannable = new SpannableString(xmppActivity.getString(isBlocked ? R.string.unblock_domain_text : R.string.block_domain_text, value)); + res = isBlocked ? R.string.unblock_domain_text : R.string.block_domain_text; } else { int resBlockAction = blockable instanceof Conversation && ((Conversation) blockable).isWithStranger() ? R.string.block_stranger : R.string.action_block_contact; builder.setTitle(isBlocked ? R.string.action_unblock_contact : resBlockAction); value = blockable.getJid().asBareJid().toString(); - spannable = new SpannableString(xmppActivity.getString(isBlocked ? R.string.unblock_contact_text : R.string.block_contact_text, value)); + res = isBlocked ? R.string.unblock_contact_text : R.string.block_contact_text; } - int start = spannable.toString().indexOf(value); - if (start >= 0) { - spannable.setSpan(new TypefaceSpan("monospace"), start, start + value.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } - binding.text.setText(spannable); + binding.text.setText(JidDialog.style(xmppActivity, res, value)); builder.setPositiveButton(isBlocked ? R.string.unblock : R.string.block, (dialog, which) -> { if (isBlocked) { xmppActivity.xmppConnectionService.sendUnblockRequest(blockable); diff --git a/src/main/java/de/pixart/messenger/ui/ConferenceDetailsActivity.java b/src/main/java/de/pixart/messenger/ui/ConferenceDetailsActivity.java index 070914bb8..901972c80 100644 --- a/src/main/java/de/pixart/messenger/ui/ConferenceDetailsActivity.java +++ b/src/main/java/de/pixart/messenger/ui/ConferenceDetailsActivity.java @@ -47,6 +47,7 @@ import de.pixart.messenger.entities.Contact; import de.pixart.messenger.entities.Conversation; import de.pixart.messenger.entities.MucOptions; import de.pixart.messenger.entities.MucOptions.User; +import de.pixart.messenger.services.EmojiService; import de.pixart.messenger.services.XmppConnectionService; import de.pixart.messenger.services.XmppConnectionService.OnConversationUpdate; import de.pixart.messenger.services.XmppConnectionService.OnMucRosterUpdate; @@ -68,6 +69,7 @@ import de.pixart.messenger.utils.XmppUri; import rocks.xmpp.addr.Jid; import static de.pixart.messenger.entities.Bookmark.printableValue; +import static de.pixart.messenger.ui.SettingsActivity.USE_BUNDLED_EMOJIS; import static de.pixart.messenger.utils.StringUtils.changed; public class ConferenceDetailsActivity extends XmppActivity implements OnConversationUpdate, OnMucRosterUpdate, XmppConnectionService.OnAffiliationChanged, XmppConnectionService.OnRoleChanged, XmppConnectionService.OnConfigurationPushed, TextWatcher, OnMediaLoaded { @@ -284,6 +286,8 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + boolean useBundledEmoji = getPreferences().getBoolean(USE_BUNDLED_EMOJIS, getResources().getBoolean(R.bool.use_bundled_emoji)); + new EmojiService(this).init(useBundledEmoji); this.binding = DataBindingUtil.setContentView(this, R.layout.activity_muc_details); this.binding.changeConferenceButton.setOnClickListener(this.mChangeConferenceSettings); this.binding.invite.setOnClickListener(inviteListener); @@ -470,9 +474,6 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers name = user.getName(); } menu.setHeaderTitle(name); - MenuItem sendPrivateMessage = menu.findItem(R.id.send_private_message); - MenuItem highlightInMuc = menu.findItem(R.id.highlight_in_muc); - highlightInMuc.setVisible(true); MucDetailsContextMenuHelper.configureMucDetailsContextMenu(this, menu, mConversation, user); } super.onCreateContextMenu(menu, v, menuInfo); @@ -531,7 +532,6 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers updateView(); } } - this.binding.detailsMucAvatar.setImageBitmap(avatarService().get(mConversation, getPixel(Config.AVATAR_SIZE))); } @Override @@ -566,6 +566,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers } else { this.binding.detailsAccount.setVisibility(View.GONE); } + this.binding.detailsMucAvatar.setImageBitmap(avatarService().get(mConversation, getPixel(Config.AVATAR_SIZE))); this.binding.yourPhoto.setImageBitmap(avatarService().get(mConversation.getAccount(), getPixel(48))); String roomName = mucOptions.getName(); String subject = mucOptions.getSubject(); @@ -593,7 +594,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers } else { this.binding.mucSubject.setVisibility(View.GONE); } - this.binding.mucYourNick.setText(mucOptions.getActualNick()); + this.binding.mucYourNick.setText(EmojiWrapper.transform(mucOptions.getActualNick())); if (mucOptions.online()) { this.binding.mucMoreDetails.setVisibility(View.VISIBLE); this.binding.mucSettings.setVisibility(View.VISIBLE); @@ -695,12 +696,8 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers Collections.sort(users); for (final User user : users) { ContactBinding binding = DataBindingUtil.inflate(inflater, R.layout.contact, this.binding.mucMembers, false); - final Contact contact = user.getContact(); - final String name = user.getName(); this.setListItemBackgroundOnView(binding.getRoot()); - if (contact != null && contact.showInRoster()) { - binding.getRoot().setOnClickListener((OnClickListener) view -> switchToContactDetails(contact)); - } + binding.getRoot().setOnClickListener(view1 -> highlightInMuc(mConversation, user.getName())); registerForContextMenu(binding.getRoot()); binding.getRoot().setTag(user); if (mAdvancedMode && user.getPgpKeyId() != 0) { @@ -708,19 +705,21 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers binding.key.setOnClickListener(v -> viewPgpKey(user)); binding.key.setText(OpenPgpUtils.convertKeyIdToHex(user.getPgpKeyId())); } + Contact contact = user.getContact(); + String name = user.getName(); if (contact != null) { - binding.contactDisplayName.setText(contact.getDisplayName()); - binding.contactJid.setText((name != null ? name + " \u2022 " : "") + getStatus(user)); + binding.contactDisplayName.setText(EmojiWrapper.transform(contact.getDisplayName())); + binding.contactJid.setText((name != null ? EmojiWrapper.transform(name) + " \u2022 " : "") + getStatus(user)); } else { - binding.contactDisplayName.setText(name == null ? "" : name); + binding.contactDisplayName.setText(name == null ? "" : EmojiWrapper.transform(name)); binding.contactJid.setText(getStatus(user)); } loadAvatar(user, binding.contactPhoto); if (user.getRole() == MucOptions.Role.NONE) { - binding.contactDisplayName.setAlpha(INACTIVE_ALPHA); - binding.key.setAlpha(INACTIVE_ALPHA); binding.contactJid.setAlpha(INACTIVE_ALPHA); + binding.key.setAlpha(INACTIVE_ALPHA); + binding.contactDisplayName.setAlpha(INACTIVE_ALPHA); binding.contactPhoto.setAlpha(INACTIVE_ALPHA); } this.binding.mucMembers.addView(binding.getRoot()); diff --git a/src/main/java/de/pixart/messenger/ui/ContactDetailsActivity.java b/src/main/java/de/pixart/messenger/ui/ContactDetailsActivity.java index 1dfc80e3c..619c2b3f2 100644 --- a/src/main/java/de/pixart/messenger/ui/ContactDetailsActivity.java +++ b/src/main/java/de/pixart/messenger/ui/ContactDetailsActivity.java @@ -51,8 +51,10 @@ import de.pixart.messenger.ui.adapter.MediaAdapter; import de.pixart.messenger.ui.interfaces.OnMediaLoaded; import de.pixart.messenger.ui.util.Attachment; import de.pixart.messenger.ui.util.GridManager; +import de.pixart.messenger.ui.util.JidDialog; import de.pixart.messenger.utils.Compatibility; import de.pixart.messenger.utils.CryptoHelper; +import de.pixart.messenger.utils.EmojiWrapper; import de.pixart.messenger.utils.IrregularUnicodeDetector; import de.pixart.messenger.utils.MenuDoubleTabUtil; import de.pixart.messenger.utils.Namespace; @@ -393,7 +395,7 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp ab.setDisplayShowCustomEnabled(true); TextView abtitle = findViewById(android.R.id.text1); TextView absubtitle = findViewById(android.R.id.text2); - abtitle.setText(contact.getDisplayName()); + abtitle.setText(EmojiWrapper.transform(contact.getDisplayName())); abtitle.setSelected(true); abtitle.setClickable(false); absubtitle.setVisibility(View.GONE); @@ -419,10 +421,10 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp binding.addContactButton.setOnClickListener(view -> { final AlertDialog.Builder deleteFromRosterDialog = new AlertDialog.Builder(ContactDetailsActivity.this); - deleteFromRosterDialog.setNegativeButton(getString(R.string.cancel), null); - deleteFromRosterDialog.setTitle(getString(R.string.action_delete_contact)); - deleteFromRosterDialog.setMessage(getString(R.string.remove_contact_text, contact.getJid().toString())); - deleteFromRosterDialog.setPositiveButton(getString(R.string.delete), removeFromRoster).create().show(); + deleteFromRosterDialog.setNegativeButton(getString(R.string.cancel), null) + .setTitle(getString(R.string.action_delete_contact)) + .setMessage(JidDialog.style(this, R.string.remove_contact_text, contact.getJid().toEscapedString())) + .setPositiveButton(getString(R.string.delete), removeFromRoster).create().show(); }); binding.detailsSendPresence.setOnCheckedChangeListener(null); binding.detailsReceivePresence.setOnCheckedChangeListener(null); @@ -443,7 +445,7 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp builder.append("\n"); } } - binding.statusMessage.setText(builder); + binding.statusMessage.setText(EmojiWrapper.transform(builder)); } String resources = contact.getPresences().getMostAvailableResource(); diff --git a/src/main/java/de/pixart/messenger/ui/ConversationFragment.java b/src/main/java/de/pixart/messenger/ui/ConversationFragment.java index a0f4bf894..f1df07e0b 100644 --- a/src/main/java/de/pixart/messenger/ui/ConversationFragment.java +++ b/src/main/java/de/pixart/messenger/ui/ConversationFragment.java @@ -32,6 +32,7 @@ import android.support.v7.view.menu.MenuBuilder; import android.support.v7.view.menu.MenuPopupHelper; import android.support.v7.widget.PopupMenu; import android.text.Editable; +import android.text.TextUtils; import android.util.Log; import android.view.ContextMenu; import android.view.ContextMenu.ContextMenuInfo; @@ -117,7 +118,6 @@ import de.pixart.messenger.utils.UIHelper; import de.pixart.messenger.xmpp.XmppConnection; import de.pixart.messenger.xmpp.chatstate.ChatState; import de.pixart.messenger.xmpp.jingle.JingleConnection; -import in.championswimmer.sfg.lib.SimpleFingerGestures; import rocks.xmpp.addr.Jid; import static de.pixart.messenger.ui.XmppActivity.EXTRA_ACCOUNT; @@ -180,7 +180,6 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke }; private boolean reInitRequiredOnStart = true; private MediaPreviewAdapter mediaPreviewAdapter; - private SimpleFingerGestures gesturesDetector = new SimpleFingerGestures(); private OnClickListener clickToMuc = new OnClickListener() { @Override @@ -625,7 +624,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke final boolean hideVoice = p.getBoolean("show_record_voice_btn", activity.getResources().getBoolean(R.bool.show_record_voice_btn)); PopupMenu popup = new PopupMenu(activity, v); popup.inflate(R.menu.choose_attachment); - Menu menu = popup.getMenu(); + final Menu menu = popup.getMenu(); ConversationMenuConfigurator.configureQuickShareAttachmentMenu(conversation, menu, hideVoice); popup.setOnMenuItemClickListener(attachmentItem -> { switch (attachmentItem.getItemId()) { @@ -658,7 +657,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke hideUnreadMessagesCount(); } else { binding.scrollToBottomButton.setEnabled(true); - binding.scrollToBottomButton.setVisibility(View.VISIBLE); + binding.scrollToBottomButton.show(); if (lastMessageUuid == null) { lastMessageUuid = conversation.getLatestMessage().getUuid(); } @@ -887,11 +886,15 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke commitAttachments(); return; } - final String body = binding.textinput.getText().toString(); + final Editable text = this.binding.textinput.getText(); + final String body = text == null ? "" : text.toString(); final Conversation conversation = this.conversation; if (body.length() == 0 || conversation == null) { return; } + if (conversation.getNextEncryption() == Message.ENCRYPTION_AXOLOTL && trustKeysIfNeeded(REQUEST_TRUST_KEYS_TEXT)) { + return; + } final Message message; if (conversation.getCorrectingMessage() == null) { message = new Message(conversation, body, conversation.getNextEncryption()); @@ -916,11 +919,6 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke case Message.ENCRYPTION_PGP: sendPgpMessage(message); break; - case Message.ENCRYPTION_AXOLOTL: - if (!trustKeysIfNeeded(REQUEST_TRUST_KEYS_TEXT)) { - sendMessage(message); - } - break; default: sendMessage(message); } @@ -989,9 +987,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke private void handlePositiveActivityResult(int requestCode, final Intent data) { switch (requestCode) { case REQUEST_TRUST_KEYS_TEXT: - final String body = binding.textinput.getText().toString(); - Message message = new Message(conversation, body, conversation.getNextEncryption()); - sendMessage(message); + sendMessage(); break; case REQUEST_TRUST_KEYS_ATTACHMENTS: commitAttachments(); @@ -1139,13 +1135,21 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke if (conversation.getMode() == Conversation.MODE_MULTI) { menuInviteContact.setVisible(true); menuArchiveChat.setTitle(R.string.action_end_conversation_muc); - menuGroupDetails.setVisible(true); - menuContactDetails.setVisible(false); } else { menuInviteContact.setVisible(false); menuArchiveChat.setTitle(R.string.action_end_conversation); + } + if (getFragmentManager().findFragmentById(R.id.secondary_fragment) != null) { + if (conversation.getMode() == Conversation.MODE_MULTI) { + menuGroupDetails.setVisible(true); + menuContactDetails.setVisible(false); + } else { + menuGroupDetails.setVisible(false); + menuContactDetails.setVisible(true); + } + } else { menuGroupDetails.setVisible(false); - menuContactDetails.setVisible(true); + menuContactDetails.setVisible(false); } menuNeedHelp.setVisible(true); menuSearchUpdates.setVisible(false); @@ -1360,20 +1364,21 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke || m.isGeoUri() || m.isXmppUri() || m.treatAsDownloadable() - || (t != null && t instanceof HttpDownloadConnection)) { + || t instanceof HttpDownloadConnection) { copyUrl.setVisible(true); } if (m.isFileOrImage() && deleted && m.hasFileOnRemoteHost()) { downloadFile.setVisible(true); downloadFile.setTitle(activity.getString(R.string.download_x_file, UIHelper.getFileDescriptionString(activity, m))); } - boolean waitingOfferedSending = m.getStatus() == Message.STATUS_WAITING + final boolean waitingOfferedSending = m.getStatus() == Message.STATUS_WAITING || m.getStatus() == Message.STATUS_UNSEND || m.getStatus() == Message.STATUS_OFFERED; - if ((t != null && !deleted) || waitingOfferedSending && m.needsUploading()) { + final boolean cancelable = (t != null && !deleted) || waitingOfferedSending && m.needsUploading(); + if (cancelable) { cancelTransmission.setVisible(true); } - if (m.isFileOrImage() && !deleted) { + if (m.isFileOrImage() && !deleted && !cancelable) { String path = m.getRelativeFilePath(); Log.d(Config.LOGTAG, "Path = " + path); if (path == null || !path.startsWith("/") || path.contains(FileBackend.getConversationsDirectory("null"))) { @@ -1381,7 +1386,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke deleteFile.setTitle(activity.getString(R.string.delete_x_file, UIHelper.getFileDescriptionString(activity, m))); } } - if (m.getStatus() == Message.STATUS_SEND_FAILED && m.getErrorMessage() != null) { + if (m.getStatus() == Message.STATUS_SEND_FAILED && m.getErrorMessage() != null && !Message.ERROR_MESSAGE_CANCELLED.equals(m.getErrorMessage())) { showErrorMessage.setVisible(true); } } @@ -1716,7 +1721,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke } builder.setView(dialogView); builder.setNegativeButton(getString(R.string.cancel), null); - builder.setPositiveButton(getString(R.string.delete_messages), (dialog, which) -> { + builder.setPositiveButton(getString(R.string.confirm), (dialog, which) -> { this.activity.xmppConnectionService.clearConversationHistory(conversation); if (endConversationCheckBox.isChecked()) { this.activity.xmppConnectionService.archiveConversation(conversation); @@ -1879,12 +1884,19 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke refresh(); } - private void deleteFile(Message message) { - if (activity.xmppConnectionService.getFileBackend().deleteFile(message)) { - message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED)); - activity.onConversationsListItemUpdated(); - refresh(); - } + private void deleteFile(final Message message) { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setNegativeButton(R.string.cancel, null); + builder.setTitle(R.string.delete_file_dialog); + builder.setMessage(R.string.delete_file_dialog_msg); + builder.setPositiveButton(R.string.confirm, (dialog, which) -> { + if (activity.xmppConnectionService.getFileBackend().deleteFile(message)) { + message.setTransferable(new TransferablePlaceholder(Transferable.STATUS_DELETED)); + activity.onConversationsListItemUpdated(); + refresh(); + } + }); + builder.create().show(); } public void resendMessage(final Message message) { @@ -1951,7 +1963,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke if (transferable != null) { transferable.cancel(); } else if (message.getStatus() != Message.STATUS_RECEIVED) { - activity.xmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED); + activity.xmppConnectionService.markMessage(message, Message.STATUS_SEND_FAILED, Message.ERROR_MESSAGE_CANCELLED); } } @@ -2207,48 +2219,6 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke this.binding.messagesView.post(this::fireReadEvent); //TODO if we only do this when this fragment is running on main it won't *bing* in tablet layout which might be unnecessary since we can *see* it activity.xmppConnectionService.getNotificationService().setOpenConversation(this.conversation); - - // todo temporarly disable swipe gestures - /* - gesturesDetector.setOnFingerGestureListener(new SimpleFingerGestures.OnFingerGestureListener() { - @Override - public boolean onSwipeUp(int fingers, long gestureDuration, double gestureDistance) { - return false; - } - - @Override - public boolean onSwipeDown(int fingers, long gestureDuration, double gestureDistance) { - return false; - } - - @Override - public boolean onSwipeLeft(int fingers, long gestureDuration, double gestureDistance) { - return false; - } - - @Override - public boolean onSwipeRight(int fingers, long gestureDuration, double gestureDistance) { - activity.onBackPressed(); - return false; - } - - @Override - public boolean onPinch(int fingers, long gestureDuration, double gestureDistance) { - return false; - } - - @Override - public boolean onUnpinch(int fingers, long gestureDuration, double gestureDistance) { - return false; - } - - @Override - public boolean onDoubleTap(int fingers) { - return false; - } - }); - this.binding.messagesView.setOnTouchListener(gesturesDetector); - */ return true; } @@ -2262,7 +2232,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke return; } this.binding.scrollToBottomButton.setEnabled(false); - this.binding.scrollToBottomButton.setVisibility(View.GONE); + this.binding.scrollToBottomButton.hide(); this.binding.unreadCountCustomView.setVisibility(View.GONE); } @@ -2282,9 +2252,11 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke final String nick = extras.getString(ConversationsActivity.EXTRA_NICK); final boolean asQuote = extras.getBoolean(ConversationsActivity.EXTRA_AS_QUOTE); final boolean pm = extras.getBoolean(ConversationsActivity.EXTRA_IS_PRIVATE_MESSAGE, false); + final boolean doNotAppend = extras.getBoolean(ConversationsActivity.EXTRA_DO_NOT_APPEND, false); final List<Uri> uris = extractUris(extras); if (uris != null && uris.size() > 0) { - mediaPreviewAdapter.addMediaPreviews(Attachment.of(getActivity(), uris)); + final List<Uri> cleanedUris = cleanUris(new ArrayList<>(uris)); + mediaPreviewAdapter.addMediaPreviews(Attachment.of(getActivity(), cleanedUris)); toggleInputMethod(); return; } @@ -2307,7 +2279,7 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke if (text != null && asQuote) { quoteText(text); } else { - appendText(text); + appendText(text, doNotAppend); } } final Message message = downloadUuid == null ? null : conversation.findMessageWithFileAndUuid(downloadUuid); @@ -2329,6 +2301,18 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke } } + private List<Uri> cleanUris(List<Uri> uris) { + Iterator<Uri> iterator = uris.iterator(); + while (iterator.hasNext()) { + final Uri uri = iterator.next(); + if (FileBackend.weOwnFile(getActivity(), uri)) { + iterator.remove(); + Toast.makeText(getActivity(), R.string.security_violation_not_attaching_file, Toast.LENGTH_SHORT).show(); + } + } + return uris; + } + private boolean showBlockSubmenu(View view) { final Jid jid = conversation.getJid(); if (jid.getLocal() == null) { @@ -2386,6 +2370,13 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke showSnackbar(R.string.remote_server_not_found, R.string.leave, leaveMuc); } break; + case REMOTE_SERVER_TIMEOUT: + if (conversation.receivedMessagesCount() > 0) { + showSnackbar(R.string.remote_server_timeout, R.string.try_again, joinMuc); + } else { + showSnackbar(R.string.remote_server_timeout, R.string.leave, leaveMuc); + } + break; case PASSWORD_REQUIRED: showSnackbar(R.string.conference_requires_password, R.string.enter_password, enterPassword); break; @@ -2832,11 +2823,16 @@ public class ConversationFragment extends XmppFragment implements EditMessage.Ke }); } - public void appendText(String text) { + public void appendText(String text, final boolean doNotAppend) { if (text == null) { return; } - String previous = this.binding.textinput.getText().toString(); + final Editable editable = this.binding.textinput.getText(); + String previous = editable == null ? "" : editable.toString(); + if (doNotAppend && !TextUtils.isEmpty(previous)) { + Toast.makeText(getActivity(), R.string.already_drafting_message, Toast.LENGTH_LONG).show(); + return; + } if (UIHelper.isLastLineQuote(previous)) { text = '\n' + text; } else if (previous.length() != 0 && !Character.isWhitespace(previous.charAt(previous.length() - 1))) { diff --git a/src/main/java/de/pixart/messenger/ui/ConversationsActivity.java b/src/main/java/de/pixart/messenger/ui/ConversationsActivity.java index 2d07be344..1f2c68159 100644 --- a/src/main/java/de/pixart/messenger/ui/ConversationsActivity.java +++ b/src/main/java/de/pixart/messenger/ui/ConversationsActivity.java @@ -108,6 +108,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio public static final String EXTRA_AS_QUOTE = "as_quote"; public static final String EXTRA_NICK = "nick"; public static final String EXTRA_IS_PRIVATE_MESSAGE = "pm"; + public static final String EXTRA_DO_NOT_APPEND = "do_not_append"; public static final String ACTION_DESTROY_MUC = "de.pixart.messenger.DESTROY_MUC"; public static final int REQUEST_OPEN_MESSAGE = 0x9876; public static final int REQUEST_PLAY_PAUSE = 0x5432; diff --git a/src/main/java/de/pixart/messenger/ui/EditAccountActivity.java b/src/main/java/de/pixart/messenger/ui/EditAccountActivity.java index be4222e78..9d5bd279b 100644 --- a/src/main/java/de/pixart/messenger/ui/EditAccountActivity.java +++ b/src/main/java/de/pixart/messenger/ui/EditAccountActivity.java @@ -73,6 +73,7 @@ import de.pixart.messenger.xmpp.XmppConnection.Features; import de.pixart.messenger.xmpp.forms.Data; import de.pixart.messenger.xmpp.pep.Avatar; import rocks.xmpp.addr.Jid; + public class EditAccountActivity extends OmemoActivity implements OnAccountUpdate, OnUpdateBlocklist, OnKeyStatusUpdated, OnCaptchaRequested, KeyChainAliasCallback, XmppConnectionService.OnShowErrorToast, XmppConnectionService.OnMamPreferencesFetched { @@ -98,6 +99,7 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat public void onClick(final View v) { final String password = binding.accountPassword.getText().toString(); final boolean wasDisabled = mAccount != null && mAccount.getStatus() == Account.State.DISABLED; + final boolean accountInfoEdited = accountInfoEdited(); if (!mInitMode && passwordChangedInMagicCreateMode()) { gotoChangePassword(password); @@ -106,7 +108,7 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat if (mInitMode && mAccount != null) { mAccount.setOption(Account.OPTION_DISABLED, false); } - if (mAccount != null && mAccount.getStatus() == Account.State.DISABLED && !accountInfoEdited()) { + if (mAccount != null && mAccount.getStatus() == Account.State.DISABLED && !accountInfoEdited) { mAccount.setOption(Account.OPTION_DISABLED, false); if (!xmppConnectionService.updateAccount(mAccount)) { Toast.makeText(EditAccountActivity.this, R.string.unable_to_update_account, Toast.LENGTH_SHORT).show(); @@ -122,7 +124,7 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat } XmppConnection connection = mAccount == null ? null : mAccount.getXmppConnection(); - boolean openRegistrationUrl = registerNewAccount && mAccount != null && mAccount.getStatus() == Account.State.REGISTRATION_WEB; + boolean openRegistrationUrl = registerNewAccount && !accountInfoEdited && mAccount != null && mAccount.getStatus() == Account.State.REGISTRATION_WEB; boolean openPaymentUrl = mAccount != null && mAccount.getStatus() == Account.State.PAYMENT_REQUIRED; final boolean redirectionWorthyStatus = openPaymentUrl || openRegistrationUrl; URL url = connection != null && redirectionWorthyStatus ? connection.getRedirectionUrl() : null; @@ -263,7 +265,8 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat if (mAccount != null && mAccount.getStatus() != Account.State.ONLINE && mFetchingAvatar) { - startActivity(new Intent(getApplicationContext(), ManageAccountActivity.class)); + //TODO: maybe better redirect to StartConversationActivity + startActivity(new Intent(this, ManageAccountActivity.class)); overridePendingTransition(R.animator.fade_in, R.animator.fade_out); finish(); } else if (mInitMode && mAccount != null && mAccount.getStatus() == Account.State.ONLINE) { @@ -486,7 +489,7 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat } else { XmppConnection connection = mAccount == null ? null : mAccount.getXmppConnection(); URL url = connection != null && mAccount.getStatus() == Account.State.REGISTRATION_WEB ? connection.getRedirectionUrl() : null; - if (url != null && this.binding.accountRegisterNew.isChecked()) { + if (url != null && this.binding.accountRegisterNew.isChecked() && !accountInfoEdited) { this.binding.saveButton.setText(R.string.open_website); } else { this.binding.saveButton.setText(R.string.next); @@ -834,7 +837,6 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat 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) { @@ -1125,7 +1127,7 @@ public class EditAccountActivity extends OmemoActivity implements OnAccountUpdat hasKeys = true; } } - if (hasKeys && Config.supportOmemo()) { + if (hasKeys && Config.supportOmemo()) { //TODO: either the button should be visible if we print an active device or the device list should be fed with reactived devices this.binding.otherDeviceKeysCard.setVisibility(View.VISIBLE); Set<Integer> otherDevices = mAccount.getAxolotlService().getOwnDeviceIds(); if (otherDevices == null || otherDevices.isEmpty()) { diff --git a/src/main/java/de/pixart/messenger/ui/MagicCreateActivity.java b/src/main/java/de/pixart/messenger/ui/MagicCreateActivity.java index b8f11be12..10c68cc1d 100644 --- a/src/main/java/de/pixart/messenger/ui/MagicCreateActivity.java +++ b/src/main/java/de/pixart/messenger/ui/MagicCreateActivity.java @@ -22,6 +22,7 @@ import java.util.List; import de.pixart.messenger.Config; import de.pixart.messenger.R; import de.pixart.messenger.entities.Account; +import de.pixart.messenger.utils.CryptoHelper; import rocks.xmpp.addr.Jid; public class MagicCreateActivity extends XmppActivity implements TextWatcher, AdapterView.OnItemSelectedListener { @@ -29,12 +30,8 @@ public class MagicCreateActivity extends XmppActivity implements TextWatcher, Ad private TextView mFullJidDisplay; private EditText mUsername; private Spinner mServer; - private SecureRandom mRandom; String domain = null; - private static final String CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456780+-/#$!?"; - private static final int PW_LENGTH = 10; - @Override protected void refreshUiReal() { @@ -74,7 +71,6 @@ public class MagicCreateActivity extends XmppActivity implements TextWatcher, Ad mServer.setSelection(defaultServer); mServer.setOnItemSelectedListener(this); adapter.setDropDownViewResource(android.R.layout.select_dialog_singlechoice); - mRandom = new SecureRandom(); Button next = findViewById(R.id.create_account); next.setOnClickListener(v -> { try { @@ -90,7 +86,7 @@ public class MagicCreateActivity extends XmppActivity implements TextWatcher, Ad mUsername.setError(null); Account account = xmppConnectionService.findAccountByJid(jid); if (account == null) { - account = new Account(jid, createPassword()); + account = new Account(jid, CryptoHelper.createPassword(new SecureRandom())); account.setOption(Account.OPTION_REGISTER, true); account.setOption(Account.OPTION_DISABLED, true); account.setOption(Account.OPTION_MAGIC_CREATE, true); @@ -115,14 +111,6 @@ public class MagicCreateActivity extends XmppActivity implements TextWatcher, Ad mUsername.addTextChangedListener(this); } - private String createPassword() { - StringBuilder builder = new StringBuilder(PW_LENGTH); - for (int i = 0; i < PW_LENGTH; ++i) { - builder.append(CHARS.charAt(mRandom.nextInt(CHARS.length() - 1))); - } - return builder.toString(); - } - @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { diff --git a/src/main/java/de/pixart/messenger/ui/ShowFullscreenMessageActivity.java b/src/main/java/de/pixart/messenger/ui/MediaViewerActivity.java index b34e49653..a8285d594 100644 --- a/src/main/java/de/pixart/messenger/ui/ShowFullscreenMessageActivity.java +++ b/src/main/java/de/pixart/messenger/ui/MediaViewerActivity.java @@ -4,6 +4,8 @@ import android.content.ActivityNotFoundException; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.BitmapFactory; @@ -13,37 +15,40 @@ import android.os.Bundle; import android.preference.PreferenceManager; import android.support.design.widget.FloatingActionButton; import android.support.v7.app.ActionBar; +import android.support.v7.view.menu.MenuBuilder; +import android.support.v7.view.menu.MenuPopupHelper; +import android.support.v7.widget.PopupMenu; import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; import android.view.View; import android.view.WindowManager; import android.webkit.MimeTypeMap; import android.widget.ImageView; import android.widget.Toast; -import com.bumptech.glide.Glide; -import com.bumptech.glide.load.resource.drawable.GlideDrawable; -import com.bumptech.glide.request.animation.GlideAnimation; -import com.bumptech.glide.request.target.GlideDrawableImageViewTarget; -import com.github.chrisbanes.photoview.PhotoView; -import com.github.chrisbanes.photoview.PhotoViewAttacher; +import com.davemorrissey.labs.subscaleview.ImageSource; +import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView; import com.github.rtoshiro.view.video.FullscreenVideoLayout; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.util.List; import de.pixart.messenger.Config; import de.pixart.messenger.R; import de.pixart.messenger.persistance.FileBackend; import de.pixart.messenger.utils.ExifHelper; +import de.pixart.messenger.utils.MimeUtils; import static de.pixart.messenger.persistance.FileBackend.close; -public class ShowFullscreenMessageActivity extends XmppActivity { +public class MediaViewerActivity extends XmppActivity { Integer oldOrientation; - PhotoView mImage; + SubsamplingScaleImageView mImage; FullscreenVideoLayout mVideo; ImageView mFullscreenbutton; Uri mFileUri; @@ -52,6 +57,22 @@ public class ShowFullscreenMessageActivity extends XmppActivity { int height = 0; int width = 0; int rotation = 0; + boolean isImage = false; + boolean isVideo = false; + + public static String getMimeType(String path) { + try { + String type = null; + String extension = path.substring(path.lastIndexOf(".") + 1, path.length()); + if (extension != null) { + type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); + } + return type; + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } @Override public void onCreate(Bundle savedInstanceState) { @@ -73,18 +94,59 @@ public class ShowFullscreenMessageActivity extends XmppActivity { getWindow().setAttributes(layout); getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); getWindow().addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN); - setContentView(R.layout.activity_fullscreen_message); + setContentView(R.layout.activity_media_viewer); mImage = findViewById(R.id.message_image_view); mVideo = findViewById(R.id.message_video_view); mFullscreenbutton = findViewById(R.id.vcv_img_fullscreen); fab = findViewById(R.id.fab); fab.setOnClickListener(v -> { - mVideo.reset(); - shareWith(mFile); + PopupMenu popup = new PopupMenu(MediaViewerActivity.this, v); + popup.inflate(R.menu.media_viewer); + final Menu menu = popup.getMenu(); + MenuItem delete = menu.findItem(R.id.action_delete); + MenuItem open = menu.findItem(R.id.action_open); + Log.d(Config.LOGTAG, "Path = " + mFile.toString()); + if (mFile == null || !mFile.toString().startsWith("/") || mFile.toString().contains(FileBackend.getConversationsDirectory("null"))) { + delete.setVisible(true); + } else { + delete.setVisible(false); + } + if (isVideo) { + if (isDarkTheme()) { + open.setIcon(R.drawable.ic_video_white_24dp); + } else { + open.setIcon(R.drawable.ic_video_black_24dp); + } + } else if (isImage) { + if (isDarkTheme()) { + open.setIcon(R.drawable.ic_image_white_24dp); + } else { + open.setIcon(R.drawable.ic_image_black_24dp); + } + } + popup.setOnMenuItemClickListener(item -> { + switch (item.getItemId()) { + case R.id.action_share: + share(); + break; + case R.id.action_open: + open(); + break; + case R.id.action_delete: + deleteFile(); + break; + default: + return false; + } + return true; + }); + MenuPopupHelper menuHelper = new MenuPopupHelper(MediaViewerActivity.this, (MenuBuilder) menu, v); + menuHelper.setForceShowIcon(true); + menuHelper.show(); }); } - private void shareWith(File mFile) { + private void share() { Intent share = new Intent(Intent.ACTION_SEND); share.setType(getMimeType(mFile.toString())); share.putExtra(Intent.EXTRA_STREAM, FileBackend.getUriForFile(this, mFile)); @@ -96,18 +158,35 @@ public class ShowFullscreenMessageActivity extends XmppActivity { } } - public static String getMimeType(String path) { + private void deleteFile() { + this.xmppConnectionService.getFileBackend().deleteFile(mFile); + finish(); + } + + private void open() { + Uri uri; try { - String type = null; - String extension = path.substring(path.lastIndexOf(".") + 1, path.length()); - if (extension != null) { - type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension); - } - return type; - } catch (Exception e) { - e.printStackTrace(); + uri = FileBackend.getUriForFile(this, mFile); + } catch (SecurityException e) { + Log.d(Config.LOGTAG, "No permission to access " + mFile.getAbsolutePath(), e); + Toast.makeText(this, this.getString(R.string.no_permission_to_access_x, mFile.getAbsolutePath()), Toast.LENGTH_SHORT).show(); + return; + } + String mime = MimeUtils.guessMimeTypeFromUri(this, uri); + Intent openIntent = new Intent(Intent.ACTION_VIEW); + openIntent.setDataAndType(uri, mime); + openIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + PackageManager manager = this.getPackageManager(); + List<ResolveInfo> info = manager.queryIntentActivities(openIntent, 0); + if (info.size() == 0) { + openIntent.setDataAndType(uri, "*/*"); + } + try { + this.startActivity(openIntent); + finish(); + } catch (ActivityNotFoundException e) { + Toast.makeText(this, R.string.no_application_found_to_open_file, Toast.LENGTH_SHORT).show(); } - return null; } @Override @@ -125,34 +204,38 @@ public class ShowFullscreenMessageActivity extends XmppActivity { mFile = new File(mFileUri.getPath()); if (mFileUri != null && mFile.exists() && mFile.length() > 0) { try { - DisplayImage(mFile); + isImage = true; + DisplayImage(mFile, mFileUri); } catch (Exception e) { + isImage = false; Log.d(Config.LOGTAG, "Illegal exeption :" + e); - Toast.makeText(ShowFullscreenMessageActivity.this, getString(R.string.error_file_corrupt), Toast.LENGTH_SHORT).show(); + Toast.makeText(MediaViewerActivity.this, getString(R.string.error_file_corrupt), Toast.LENGTH_SHORT).show(); finish(); } } else { - Toast.makeText(ShowFullscreenMessageActivity.this, getString(R.string.file_deleted), Toast.LENGTH_SHORT).show(); + Toast.makeText(MediaViewerActivity.this, getString(R.string.file_deleted), Toast.LENGTH_SHORT).show(); } } else if (intent.hasExtra("video")) { mFileUri = intent.getParcelableExtra("video"); mFile = new File(mFileUri.getPath()); if (mFileUri != null && mFile.exists() && mFile.length() > 0) { try { + isVideo = true; DisplayVideo(mFileUri); } catch (Exception e) { + isVideo = false; Log.d(Config.LOGTAG, "Illegal exeption :" + e); - Toast.makeText(ShowFullscreenMessageActivity.this, getString(R.string.error_file_corrupt), Toast.LENGTH_SHORT).show(); + Toast.makeText(MediaViewerActivity.this, getString(R.string.error_file_corrupt), Toast.LENGTH_SHORT).show(); finish(); } } else { - Toast.makeText(ShowFullscreenMessageActivity.this, getString(R.string.file_deleted), Toast.LENGTH_SHORT).show(); + Toast.makeText(MediaViewerActivity.this, getString(R.string.file_deleted), Toast.LENGTH_SHORT).show(); } } } } - private void DisplayImage(final File file) { + private void DisplayImage(final File file, final Uri FileUri) { BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeFile(new File(file.getPath()).getAbsolutePath(), options); @@ -163,19 +246,9 @@ public class ShowFullscreenMessageActivity extends XmppActivity { if (useAutoRotateScreen()) { rotateScreen(width, height, rotation); } - final PhotoViewAttacher mAttacher = new PhotoViewAttacher(mImage); mImage.setVisibility(View.VISIBLE); try { - Glide.with(this) - .load(file) - .dontAnimate() - .into(new GlideDrawableImageViewTarget(mImage) { - @Override - public void onResourceReady(GlideDrawable resource, GlideAnimation<? super GlideDrawable> animation) { - super.onResourceReady(resource, animation); - mAttacher.update(); - } - }); + mImage.setImage(ImageSource.uri(FileUri)); } catch (Exception e) { Toast.makeText(this, getString(R.string.error_file_corrupt), Toast.LENGTH_LONG).show(); e.printStackTrace(); diff --git a/src/main/java/de/pixart/messenger/ui/ShareViaAccountActivity.java b/src/main/java/de/pixart/messenger/ui/ShareViaAccountActivity.java index 7aa0d3f64..2d535a44c 100644 --- a/src/main/java/de/pixart/messenger/ui/ShareViaAccountActivity.java +++ b/src/main/java/de/pixart/messenger/ui/ShareViaAccountActivity.java @@ -2,9 +2,6 @@ package de.pixart.messenger.ui; import android.os.Bundle; import android.support.v7.app.ActionBar; -import android.view.View; -import android.widget.AdapterView; -import android.widget.AdapterView.OnItemClickListener; import android.widget.ListView; import java.util.ArrayList; @@ -46,27 +43,22 @@ public class ShareViaAccountActivity extends XmppActivity { setSupportActionBar(findViewById(R.id.toolbar)); configureActionBar(getSupportActionBar()); accountListView = findViewById(R.id.account_list); - this.mAccountAdapter = new AccountAdapter(this, accountList); + this.mAccountAdapter = new AccountAdapter(this, accountList, false); accountListView.setAdapter(this.mAccountAdapter); - accountListView.setOnItemClickListener(new OnItemClickListener() { - - @Override - public void onItemClick(AdapterView<?> arg0, View view, - int position, long arg3) { - final Account account = accountList.get(position); - final String body = getIntent().getStringExtra(EXTRA_BODY); - - try { - final Jid contact = Jid.of(getIntent().getStringExtra(EXTRA_CONTACT)); - final Conversation conversation = xmppConnectionService.findOrCreateConversation( - account, contact, false, false); - switchToConversation(conversation, body, false); - } catch (IllegalArgumentException e) { - // ignore error - } + accountListView.setOnItemClickListener((arg0, view, position, arg3) -> { + final Account account = accountList.get(position); + final String body = getIntent().getStringExtra(EXTRA_BODY); - finish(); + try { + final Jid contact = Jid.of(getIntent().getStringExtra(EXTRA_CONTACT)); + final Conversation conversation = xmppConnectionService.findOrCreateConversation( + account, contact, false, false); + switchToConversation(conversation, body); + } catch (IllegalArgumentException e) { + // ignore error } + + finish(); }); } @@ -91,7 +83,7 @@ public class ShareViaAccountActivity extends XmppActivity { final Jid contact = Jid.of(getIntent().getStringExtra(EXTRA_CONTACT)); final Conversation conversation = xmppConnectionService.findOrCreateConversation( account, contact, false, false); - switchToConversation(conversation, body, false); + switchToConversation(conversation, body); } catch (IllegalArgumentException e) { // ignore error } diff --git a/src/main/java/de/pixart/messenger/ui/ShowLocationActivity.java b/src/main/java/de/pixart/messenger/ui/ShowLocationActivity.java index b3ff43d3d..a12d398f2 100644 --- a/src/main/java/de/pixart/messenger/ui/ShowLocationActivity.java +++ b/src/main/java/de/pixart/messenger/ui/ShowLocationActivity.java @@ -34,9 +34,9 @@ import de.pixart.messenger.utils.MenuDoubleTabUtil; import static de.pixart.messenger.ui.SettingsActivity.USE_BUNDLED_EMOJIS; public class ShowLocationActivity extends XmppActivity { + FloatingActionButton fab; private Location location; private String mLocationName; - FloatingActionButton fab; private static String getAddress(Context context, Location location) { double longitude = location.getLongitude(); @@ -159,6 +159,22 @@ public class ShowLocationActivity extends XmppActivity { } } + private void navigate(Location location) { + if (location == null) { + Log.d(Config.LOGTAG, "No location given"); + return; + } + double longitude = location.getLongitude(); + double latitude = location.getLatitude(); + try { + Intent intent = new Intent(android.content.Intent.ACTION_VIEW, Uri.parse("google.navigation:q=" + String.valueOf(latitude) + "," + String.valueOf(longitude))); + startActivity(intent); + overridePendingTransition(R.animator.fade_in, R.animator.fade_out); + } catch (ActivityNotFoundException e) { + Toast.makeText(this, R.string.no_application_found_to_display_location, Toast.LENGTH_SHORT).show(); + } + } + private class getAddressAsync extends AsyncTask<Void, Void, Void> { String address = null; @@ -186,20 +202,4 @@ public class ShowLocationActivity extends XmppActivity { showLocation(location, address); } } - - private void navigate (Location location) { - if (location == null) { - Log.d(Config.LOGTAG, "No location given"); - return; - } - double longitude = location.getLongitude(); - double latitude = location.getLatitude(); - try { - Intent intent = new Intent(android.content.Intent.ACTION_VIEW, Uri.parse("geo:" + String.valueOf(latitude) + "," + String.valueOf(longitude))); - startActivity(intent); - overridePendingTransition(R.animator.fade_in, R.animator.fade_out); - } catch (ActivityNotFoundException e) { - Toast.makeText(this, R.string.no_application_found_to_display_location, Toast.LENGTH_SHORT).show(); - } - } }
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/ui/StartConversationActivity.java b/src/main/java/de/pixart/messenger/ui/StartConversationActivity.java index 52bff613c..5b337abb6 100644 --- a/src/main/java/de/pixart/messenger/ui/StartConversationActivity.java +++ b/src/main/java/de/pixart/messenger/ui/StartConversationActivity.java @@ -26,10 +26,7 @@ import android.support.v7.app.ActionBar; import android.support.v7.app.AlertDialog; import android.support.v7.widget.Toolbar; import android.text.Editable; -import android.text.SpannableString; -import android.text.Spanned; import android.text.TextWatcher; -import android.text.style.TypefaceSpan; import android.util.Log; import android.util.Pair; import android.view.ContextMenu; @@ -71,6 +68,7 @@ import de.pixart.messenger.services.XmppConnectionService; import de.pixart.messenger.services.XmppConnectionService.OnRosterUpdate; import de.pixart.messenger.ui.adapter.ListItemAdapter; import de.pixart.messenger.ui.interfaces.OnBackendConnected; +import de.pixart.messenger.ui.util.JidDialog; import de.pixart.messenger.ui.util.PendingItem; import de.pixart.messenger.ui.util.SoftKeyboardUtils; import de.pixart.messenger.utils.MenuDoubleTabUtil; @@ -429,7 +427,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne final AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setNegativeButton(R.string.cancel, null); builder.setTitle(R.string.action_delete_contact); - builder.setMessage(getString(R.string.remove_contact_text, contact.getJid())); + builder.setMessage(JidDialog.style(this, R.string.remove_contact_text, contact.getJid().toEscapedString())); builder.setPositiveButton(R.string.delete, (dialog, which) -> { xmppConnectionService.deleteContactOnServer(contact); filter(mSearchEditText.getText().toString()); @@ -444,7 +442,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setNegativeButton(R.string.cancel, null); builder.setTitle(R.string.delete_bookmark); - builder.setMessage(getString(R.string.remove_bookmark_text, bookmark.getJid())); + builder.setMessage(JidDialog.style(this, R.string.remove_bookmark_text, bookmark.getJid().toEscapedString())); builder.setPositiveButton(R.string.delete, (dialog, which) -> { bookmark.setConversation(null); Account account = bookmark.getAccount(); @@ -495,7 +493,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne contact.setServerName(invite.getName()); } if (contact.isSelf()) { - switchToConversation(contact, null); + switchToConversation(contact); return true; } else if (contact.showInRoster()) { throw new EnterJidDialog.JidError(getString(R.string.contact_already_exists)); @@ -504,7 +502,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne if (invite != null && invite.hasFingerprints()) { xmppConnectionService.verifyFingerprints(contact, invite.getFingerprints()); } - switchToConversation(contact, invite == null ? null : invite.getBody()); + switchToConversationDoNotAppend(contact, invite == null ? null : invite.getBody()); return true; } }); @@ -552,9 +550,14 @@ public class StartConversationActivity extends XmppActivity implements XmppConne return xmppConnectionService.findAccountByJid(jid); } - protected void switchToConversation(Contact contact, String body) { + protected void switchToConversation(Contact contact) { Conversation conversation = xmppConnectionService.findOrCreateConversation(contact.getAccount(), contact.getJid(), false, true); - switchToConversation(conversation, body, false); + switchToConversation(conversation); + } + + protected void switchToConversationDoNotAppend(Contact contact, String body) { + Conversation conversation = xmppConnectionService.findOrCreateConversation(contact.getAccount(), contact.getJid(), false, true); + switchToConversationDoNotAppend(conversation, body); } @Override @@ -790,7 +793,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne if (invite.isAction(XmppUri.ACTION_JOIN)) { Conversation muc = xmppConnectionService.findFirstMuc(invite.getJid()); if (muc != null) { - switchToConversation(muc, invite.getBody(), false); + switchToConversationDoNotAppend(muc, invite.getBody()); return true; } else { showJoinConferenceDialog(invite.getJid().asBareJid().toString()); @@ -812,7 +815,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne if (invite.account != null) { xmppConnectionService.getShortcutService().report(contact); } - switchToConversation(contact, invite.getBody()); + switchToConversationDoNotAppend(contact, invite.getBody()); } return true; } else { @@ -834,19 +837,13 @@ public class StartConversationActivity extends XmppActivity implements XmppConne View view = getLayoutInflater().inflate(R.layout.dialog_verify_fingerprints, null); final CheckBox isTrustedSource = view.findViewById(R.id.trusted_source); TextView warning = view.findViewById(R.id.warning); - String jid = contact.getJid().asBareJid().toString(); - SpannableString spannable = new SpannableString(getString(R.string.verifying_omemo_keys_trusted_source, jid, contact.getDisplayName())); - int start = spannable.toString().indexOf(jid); - if (start >= 0) { - spannable.setSpan(new TypefaceSpan("monospace"), start, start + jid.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } - warning.setText(spannable); + warning.setText(JidDialog.style(this, R.string.verifying_omemo_keys_trusted_source, contact.getJid().asBareJid().toEscapedString(), contact.getDisplayName())); builder.setView(view); builder.setPositiveButton(R.string.confirm, (dialog, which) -> { if (isTrustedSource.isChecked() && invite.hasFingerprints()) { xmppConnectionService.verifyFingerprints(contact, invite.getFingerprints()); } - switchToConversation(contact, invite.getBody()); + switchToConversationDoNotAppend(contact, invite.getBody()); }); builder.setNegativeButton(R.string.cancel, (dialog, which) -> StartConversationActivity.this.finish()); AlertDialog dialog = builder.create(); @@ -1128,13 +1125,13 @@ public class StartConversationActivity extends XmppActivity implements XmppConne @Override public void destroyItem(@NonNull ViewGroup container, int position, @NonNull Object object) { - assert (0 <= position && position < fragments.length); FragmentTransaction trans = fragmentManager.beginTransaction(); trans.remove(fragments[position]); trans.commit(); fragments[position] = null; } + @NonNull @Override public Fragment instantiateItem(@NonNull ViewGroup container, int position) { Fragment fragment = getItem(position); @@ -1167,8 +1164,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne } } - public Fragment getItem(int position) { - assert (0 <= position && position < fragments.length); + Fragment getItem(int position) { if (fragments[position] == null) { final MyListFragment listFragment = new MyListFragment(); if (position == 1) { diff --git a/src/main/java/de/pixart/messenger/ui/XmppActivity.java b/src/main/java/de/pixart/messenger/ui/XmppActivity.java index d01f29ac5..dcf3afed4 100644 --- a/src/main/java/de/pixart/messenger/ui/XmppActivity.java +++ b/src/main/java/de/pixart/messenger/ui/XmppActivity.java @@ -447,15 +447,19 @@ public abstract class XmppActivity extends ActionBarActivity { } public void switchToConversation(Conversation conversation) { - switchToConversation(conversation, null, false); + switchToConversation(conversation, null); } public void switchToConversationAndQuote(Conversation conversation, String text) { switchToConversation(conversation, text, true, null, false, false); } - public void switchToConversation(Conversation conversation, String text, boolean newTask) { - switchToConversation(conversation, text, false, null, false, newTask); + public void switchToConversation(Conversation conversation, String text) { + switchToConversation(conversation, text, false, null, false, false); + } + + public void switchToConversationDoNotAppend(Conversation conversation, String text) { + switchToConversation(conversation, text, false, null, false, true); } public void highlightInMuc(Conversation conversation, String nick) { @@ -466,7 +470,7 @@ public abstract class XmppActivity extends ActionBarActivity { switchToConversation(conversation, null, false, nick, true, false); } - private void switchToConversation(Conversation conversation, String text, boolean asQuote, String nick, boolean pm, boolean newTask) { + private void switchToConversation(Conversation conversation, String text, boolean asQuote, String nick, boolean pm, boolean doNotAppend) { Intent intent = new Intent(this, ConversationsActivity.class); intent.setAction(ConversationsActivity.ACTION_VIEW_CONVERSATION); intent.putExtra(ConversationsActivity.EXTRA_CONVERSATION, conversation.getUuid()); @@ -480,13 +484,10 @@ public abstract class XmppActivity extends ActionBarActivity { intent.putExtra(ConversationsActivity.EXTRA_NICK, nick); intent.putExtra(ConversationsActivity.EXTRA_IS_PRIVATE_MESSAGE, pm); } - if (newTask) { - intent.setFlags(intent.getFlags() - | Intent.FLAG_ACTIVITY_NEW_TASK - | Intent.FLAG_ACTIVITY_SINGLE_TOP); - } else { - intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_CLEAR_TOP); + if (doNotAppend) { + intent.putExtra(ConversationsActivity.EXTRA_DO_NOT_APPEND, true); } + intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(intent); overridePendingTransition(R.animator.fade_in, R.animator.fade_out); finish(); @@ -598,18 +599,6 @@ public abstract class XmppActivity extends ActionBarActivity { } } - protected boolean noAccountUsesPgp() { - if (!hasPgp()) { - return true; - } - for (Account account : xmppConnectionService.getAccounts()) { - if (account.getPgpId() != 0) { - return false; - } - } - return true; - } - @SuppressWarnings("deprecation") @TargetApi(Build.VERSION_CODES.JELLY_BEAN) protected void setListItemBackgroundOnView(View view) { diff --git a/src/main/java/de/pixart/messenger/ui/adapter/ConversationAdapter.java b/src/main/java/de/pixart/messenger/ui/adapter/ConversationAdapter.java index 55dd6d65f..d61510611 100644 --- a/src/main/java/de/pixart/messenger/ui/adapter/ConversationAdapter.java +++ b/src/main/java/de/pixart/messenger/ui/adapter/ConversationAdapter.java @@ -32,6 +32,7 @@ import de.pixart.messenger.entities.Transferable; import de.pixart.messenger.ui.ConversationFragment; import de.pixart.messenger.ui.XmppActivity; import de.pixart.messenger.ui.util.StyledAttributes; +import de.pixart.messenger.ui.widget.FailedCountCustomView; import de.pixart.messenger.ui.widget.UnreadCountCustomView; import de.pixart.messenger.utils.EmojiWrapper; import de.pixart.messenger.utils.IrregularUnicodeDetector; @@ -125,7 +126,7 @@ public class ConversationAdapter extends RecyclerView.Adapter<ConversationAdapte } if (failedCount > 0) { viewHolder.failedCount.setVisibility(View.VISIBLE); - viewHolder.failedCount.setUnreadCount(failedCount); + viewHolder.failedCount.setFailedCount(failedCount); } else { viewHolder.failedCount.setVisibility(View.GONE); } @@ -356,7 +357,7 @@ public class ConversationAdapter extends RecyclerView.Adapter<ConversationAdapte private TextView sender; private ImageView notificationIcon; private UnreadCountCustomView unreadCount; - private UnreadCountCustomView failedCount; + private FailedCountCustomView failedCount; private ImageView receivedStatus; private ImageView readStatus; private ImageView avatar; diff --git a/src/main/java/de/pixart/messenger/ui/adapter/ListItemAdapter.java b/src/main/java/de/pixart/messenger/ui/adapter/ListItemAdapter.java index 17479cf99..e99353f13 100644 --- a/src/main/java/de/pixart/messenger/ui/adapter/ListItemAdapter.java +++ b/src/main/java/de/pixart/messenger/ui/adapter/ListItemAdapter.java @@ -8,7 +8,6 @@ import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.AsyncTask; import android.preference.PreferenceManager; -import android.support.text.emoji.widget.EmojiTextView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -28,6 +27,7 @@ import de.pixart.messenger.entities.ListItem; import de.pixart.messenger.ui.SettingsActivity; import de.pixart.messenger.ui.XmppActivity; import de.pixart.messenger.ui.util.StyledAttributes; +import de.pixart.messenger.utils.EmojiWrapper; import de.pixart.messenger.utils.IrregularUnicodeDetector; import de.pixart.messenger.utils.UIHelper; import rocks.xmpp.addr.Jid; @@ -97,7 +97,7 @@ public class ListItemAdapter extends ArrayAdapter<ListItem> { } else { viewHolder.jid.setVisibility(View.GONE); } - viewHolder.name.setText(item.getDisplayName()); + viewHolder.name.setText(EmojiWrapper.transform(item.getDisplayName())); if (tags.size() != 0) { for (ListItem.Tag tag : tags) { offline = tag.getOffline() == 1; @@ -220,7 +220,7 @@ public class ListItemAdapter extends ArrayAdapter<ListItem> { } private static class ViewHolder { - private EmojiTextView name; + private TextView name; private TextView jid; private ImageView avatar; private FlowLayout tags; diff --git a/src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java b/src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java index b13ccb910..bc65fd68a 100644 --- a/src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java +++ b/src/main/java/de/pixart/messenger/ui/adapter/MessageAdapter.java @@ -275,7 +275,11 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie if (isResendable && file.exists()) { info = getContext().getString(R.string.send_failed_resend); } else { - info = getContext().getString(R.string.send_failed); + if (Message.ERROR_MESSAGE_CANCELLED.equals(message.getErrorMessage())) { + info = getContext().getString(R.string.cancelled); + } else { + info = getContext().getString(R.string.send_failed); + } } error = true; break; @@ -408,10 +412,15 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie viewHolder.download_button.setText(add_contact); viewHolder.download_button.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_account_card_details_grey600_48dp, 0, 0, 0); viewHolder.download_button.setOnClickListener(v -> { - Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setData(Uri.parse(body)); - activity.startActivity(intent); - activity.overridePendingTransition(R.animator.fade_in, R.animator.fade_out); + try { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse(body)); + activity.startActivity(intent); + activity.overridePendingTransition(R.animator.fade_in, R.animator.fade_out); + } catch (Exception e) { + Toast.makeText(activity, R.string.no_application_found_to_view_contact, Toast.LENGTH_LONG).show(); + } + }); viewHolder.image.setVisibility(View.GONE); viewHolder.messageBody.setVisibility(View.GONE); @@ -635,7 +644,7 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie e.printStackTrace(); } } - viewHolder.download_button.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_file_grey600_48dp, 0, 0, 0); + viewHolder.download_button.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_android_grey600_48dp, 0, 0, 0); viewHolder.download_button.setText(activity.getString(R.string.open_x_file, UIHelper.getFileDescriptionString(activity, message) + APKName)); } @@ -839,7 +848,7 @@ public class MessageAdapter extends ArrayAdapter<Message> implements CopyTextVie } else { viewHolder.status_message.setVisibility(View.VISIBLE); viewHolder.load_more_messages.setVisibility(View.GONE); - viewHolder.status_message.setText(message.getBody()); + viewHolder.status_message.setText(EmojiWrapper.transform(message.getBody())); boolean showAvatar; if (conversation.getMode() == Conversation.MODE_SINGLE) { showAvatar = true; diff --git a/src/main/java/de/pixart/messenger/ui/util/ConversationMenuConfigurator.java b/src/main/java/de/pixart/messenger/ui/util/ConversationMenuConfigurator.java index edca88f78..dd69b2337 100644 --- a/src/main/java/de/pixart/messenger/ui/util/ConversationMenuConfigurator.java +++ b/src/main/java/de/pixart/messenger/ui/util/ConversationMenuConfigurator.java @@ -66,6 +66,9 @@ public class ConversationMenuConfigurator { } public static void configureAttachmentMenu(@NonNull Conversation conversation, Menu menu, Boolean Quick_share_attachment_choice, boolean hasAttachments) { + if (menu == null) { + return; + } final MenuItem menuAttach = menu.findItem(R.id.action_attach_file); if (Quick_share_attachment_choice && !hasAttachments) { menuAttach.setVisible(false); diff --git a/src/main/java/de/pixart/messenger/ui/util/JidDialog.java b/src/main/java/de/pixart/messenger/ui/util/JidDialog.java new file mode 100644 index 000000000..291bb873c --- /dev/null +++ b/src/main/java/de/pixart/messenger/ui/util/JidDialog.java @@ -0,0 +1,22 @@ +package de.pixart.messenger.ui.util; + +import android.content.Context; +import android.support.annotation.StringRes; +import android.text.SpannableString; +import android.text.Spanned; +import android.text.style.TypefaceSpan; + +public class JidDialog { + + public static SpannableString style(Context context, @StringRes int res, String... args) { + SpannableString spannable = new SpannableString(context.getString(res, (Object[]) args)); + if (args.length >= 1) { + final String value = args[0]; + int start = spannable.toString().indexOf(value); + if (start >= 0) { + spannable.setSpan(new TypefaceSpan("monospace"), start, start + value.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + return spannable; + } +}
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/ui/util/MucDetailsContextMenuHelper.java b/src/main/java/de/pixart/messenger/ui/util/MucDetailsContextMenuHelper.java index dc338e02c..f4777e8f3 100644 --- a/src/main/java/de/pixart/messenger/ui/util/MucDetailsContextMenuHelper.java +++ b/src/main/java/de/pixart/messenger/ui/util/MucDetailsContextMenuHelper.java @@ -25,6 +25,8 @@ import rocks.xmpp.addr.Jid; public final class MucDetailsContextMenuHelper { public static void configureMucDetailsContextMenu(Activity activity, Menu menu, Conversation conversation, User user) { final boolean advancedMode = PreferenceManager.getDefaultSharedPreferences(activity).getBoolean("advanced_muc_mode", false); + final MucOptions mucOptions = conversation.getMucOptions(); + MenuItem sendPrivateMessage = menu.findItem(R.id.send_private_message); if (user != null && user.getRealJid() != null) { MenuItem showContactDetails = menu.findItem(R.id.action_contact_details); MenuItem startConversation = menu.findItem(R.id.start_conversation); @@ -35,6 +37,7 @@ public final class MucDetailsContextMenuHelper { MenuItem removeFromRoom = menu.findItem(R.id.remove_from_room); MenuItem banFromConference = menu.findItem(R.id.ban_from_conference); MenuItem invite = menu.findItem(R.id.invite); + MenuItem highlightInMuc = menu.findItem(R.id.highlight_in_muc); startConversation.setVisible(true); final Contact contact = user.getContact(); final User self = conversation.getMucOptions().getSelf(); @@ -44,6 +47,11 @@ public final class MucDetailsContextMenuHelper { if (activity instanceof ConferenceDetailsActivity && user.getRole() == MucOptions.Role.NONE) { invite.setVisible(true); } + if (activity instanceof ConversationsActivity) { + highlightInMuc.setVisible(false); + } else if (activity instanceof ConferenceDetailsActivity) { + highlightInMuc.setVisible(true); + } if (self.getAffiliation().ranks(MucOptions.Affiliation.ADMIN) && self.getAffiliation().outranks(user.getAffiliation())) { if (advancedMode) { @@ -66,9 +74,9 @@ public final class MucDetailsContextMenuHelper { removeAdminPrivileges.setVisible(true); } } + sendPrivateMessage.setVisible(true); + sendPrivateMessage.setEnabled(mucOptions.allowPm()); } else { - final MucOptions mucOptions = conversation.getMucOptions(); - MenuItem sendPrivateMessage = menu.findItem(R.id.send_private_message); sendPrivateMessage.setVisible(true); sendPrivateMessage.setEnabled(user != null && mucOptions.allowPm() && user.getRole().ranks(MucOptions.Role.VISITOR)); } @@ -122,6 +130,9 @@ public final class MucDetailsContextMenuHelper { case R.id.invite: activity.xmppConnectionService.directInvite(conversation, jid); return true; + case R.id.highlight_in_muc: + activity.highlightInMuc(conversation, user.getName()); + return true; default: return false; } diff --git a/src/main/java/de/pixart/messenger/ui/util/ViewUtil.java b/src/main/java/de/pixart/messenger/ui/util/ViewUtil.java index b1905479b..07e25e31d 100644 --- a/src/main/java/de/pixart/messenger/ui/util/ViewUtil.java +++ b/src/main/java/de/pixart/messenger/ui/util/ViewUtil.java @@ -15,7 +15,7 @@ import java.util.List; import de.pixart.messenger.Config; import de.pixart.messenger.R; import de.pixart.messenger.persistance.FileBackend; -import de.pixart.messenger.ui.ShowFullscreenMessageActivity; +import de.pixart.messenger.ui.MediaViewerActivity; public class ViewUtil { @@ -36,7 +36,7 @@ public class ViewUtil { } // use internal viewer for images and videos if (mime.startsWith("image/")) { - Intent intent = new Intent(context, ShowFullscreenMessageActivity.class); + Intent intent = new Intent(context, MediaViewerActivity.class); intent.putExtra("image", Uri.fromFile(file)); try { context.startActivity(intent); @@ -45,7 +45,7 @@ public class ViewUtil { //ignored } } else if (mime.startsWith("video/")) { - Intent intent = new Intent(context, ShowFullscreenMessageActivity.class); + Intent intent = new Intent(context, MediaViewerActivity.class); intent.putExtra("video", Uri.fromFile(file)); try { context.startActivity(intent); diff --git a/src/main/java/de/pixart/messenger/ui/widget/FailedCountCustomView.java b/src/main/java/de/pixart/messenger/ui/widget/FailedCountCustomView.java new file mode 100644 index 000000000..261ace3a4 --- /dev/null +++ b/src/main/java/de/pixart/messenger/ui/widget/FailedCountCustomView.java @@ -0,0 +1,76 @@ +package de.pixart.messenger.ui.widget; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Typeface; +import android.support.v4.content.ContextCompat; +import android.util.AttributeSet; +import android.view.View; + +import de.pixart.messenger.R; + +public class FailedCountCustomView extends View { + + private int count; + private Paint paint, textPaint; + private int backgroundColor = 0xffd50000; + + public FailedCountCustomView(Context context) { + super(context); + init(); + } + + public FailedCountCustomView(Context context, AttributeSet attrs) { + super(context, attrs); + initXMLAttrs(context, attrs); + init(); + } + + public FailedCountCustomView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + initXMLAttrs(context, attrs); + init(); + } + + private void initXMLAttrs(Context context, AttributeSet attrs) { + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.UnreadCountCustomView); + //setBackgroundColor(a.getColor(a.getIndex(0), ContextCompat.getColor(context, R.color.accent))); + setBackgroundColor(ContextCompat.getColor(context, R.color.red700)); + a.recycle(); + } + + void init() { + paint = new Paint(); + paint.setColor(backgroundColor); + paint.setAntiAlias(true); + textPaint = new Paint(); + textPaint.setColor(Color.WHITE); + textPaint.setTextAlign(Paint.Align.CENTER); + textPaint.setAntiAlias(true); + textPaint.setTypeface(Typeface.create(Typeface.DEFAULT, Typeface.BOLD)); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + float midx = canvas.getWidth() / 2.0f; + float midy = canvas.getHeight() / 2.0f; + float radius = Math.min(canvas.getWidth(), canvas.getHeight()) / 2.0f; + float textOffset = canvas.getWidth() / 6.0f; + textPaint.setTextSize(0.95f * radius); + canvas.drawCircle(midx, midy, radius * 0.94f, paint); + canvas.drawText(count > 999 ? "\u221E" : String.valueOf(count), midx, midy + textOffset, textPaint); + } + + public void setFailedCount(int count) { + this.count = count; + invalidate(); + } + + public void setBackgroundColor(int backgroundColor) { + this.backgroundColor = backgroundColor; + } +} diff --git a/src/main/java/de/pixart/messenger/utils/Compatibility.java b/src/main/java/de/pixart/messenger/utils/Compatibility.java index 6d5f35bc4..f178f17c7 100644 --- a/src/main/java/de/pixart/messenger/utils/Compatibility.java +++ b/src/main/java/de/pixart/messenger/utils/Compatibility.java @@ -37,8 +37,8 @@ public class Compatibility { return Build.VERSION.SDK_INT >= Build.VERSION_CODES.O; } - public static boolean twentyTwo() { - return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1; + public static boolean twentyEight() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.P; } private static boolean getBooleanPreference(Context context, String name, @BoolRes int res) { diff --git a/src/main/java/de/pixart/messenger/utils/CryptoHelper.java b/src/main/java/de/pixart/messenger/utils/CryptoHelper.java index d34823d86..e16d0d012 100644 --- a/src/main/java/de/pixart/messenger/utils/CryptoHelper.java +++ b/src/main/java/de/pixart/messenger/utils/CryptoHelper.java @@ -35,15 +35,15 @@ import rocks.xmpp.addr.Jid; public final class CryptoHelper { + public static final Pattern UUID_PATTERN = Pattern.compile("[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}"); + final public static byte[] ONE = new byte[]{0, 0, 0, 1}; + private static final char[] CHARS = "ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz123456789+-/#$!?".toCharArray(); + private static final int PW_LENGTH = 10; private static final char[] VOWELS = "aeiou".toCharArray(); private static final char[] CONSONANTS = "bcfghjklmnpqrstvwxyz".toCharArray(); - public static final String FILETRANSFER = "?FILETRANSFERv1:"; private final static char[] hexArray = "0123456789abcdef".toCharArray(); - public static final Pattern UUID_PATTERN = Pattern.compile("[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}"); - final public static byte[] ONE = new byte[]{0, 0, 0, 1}; - public static String bytesToHex(byte[] bytes) { char[] hexChars = new char[bytes.length * 2]; for (int j = 0; j < bytes.length; j++) { @@ -54,6 +54,14 @@ public final class CryptoHelper { return new String(hexChars); } + public static String createPassword(SecureRandom random) { + StringBuilder builder = new StringBuilder(PW_LENGTH); + for (int i = 0; i < PW_LENGTH; ++i) { + builder.append(CHARS[random.nextInt(CHARS.length - 1)]); + } + return builder.toString(); + } + public static String pronounceable(SecureRandom random) { char[] output = new char[random.nextInt(4) * 2 + 5]; boolean vowel = random.nextBoolean(); @@ -293,4 +301,4 @@ public final class CryptoHelper { final String u = url.toLowerCase(); return !u.contains(" ") && (u.startsWith("https://") || u.startsWith("http://") || u.startsWith("p1s3://")) && u.endsWith(".pgp"); } -} +}
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/utils/PRNGFixes.java b/src/main/java/de/pixart/messenger/utils/PRNGFixes.java index 211ebca8e..c26fe9f13 100644 --- a/src/main/java/de/pixart/messenger/utils/PRNGFixes.java +++ b/src/main/java/de/pixart/messenger/utils/PRNGFixes.java @@ -133,6 +133,58 @@ public final class PRNGFixes { } /** + * Generates a device- and invocation-specific seed to be mixed into the + * Linux PRNG. + */ + private static byte[] generateSeed() { + try { + ByteArrayOutputStream seedBuffer = new ByteArrayOutputStream(); + DataOutputStream seedBufferOut = new DataOutputStream(seedBuffer); + seedBufferOut.writeLong(System.currentTimeMillis()); + seedBufferOut.writeLong(System.nanoTime()); + seedBufferOut.writeInt(Process.myPid()); + seedBufferOut.writeInt(Process.myUid()); + seedBufferOut.write(BUILD_FINGERPRINT_AND_DEVICE_SERIAL); + seedBufferOut.close(); + return seedBuffer.toByteArray(); + } catch (IOException e) { + throw new SecurityException("Failed to generate seed", e); + } + } + + /** + * Gets the hardware serial number of this device. + * + * @return serial number or {@code null} if not available. + */ + private static String getDeviceSerialNumber() { + // We're using the Reflection API because Build.SERIAL is only available + // since API Level 9 (Gingerbread, Android 2.3). + try { + return (String) Build.class.getField("SERIAL").get(null); + } catch (Exception ignored) { + return null; + } + } + + private static byte[] getBuildFingerprintAndDeviceSerial() { + StringBuilder result = new StringBuilder(); + String fingerprint = Build.FINGERPRINT; + if (fingerprint != null) { + result.append(fingerprint); + } + String serial = getDeviceSerialNumber(); + if (serial != null) { + result.append(serial); + } + try { + return result.toString().getBytes("UTF-8"); + } catch (UnsupportedEncodingException e) { + throw new RuntimeException("UTF-8 encoding not supported"); + } + } + + /** * {@code Provider} of {@code SecureRandom} engines which pass through all * requests to the Linux PRNG. */ @@ -157,17 +209,17 @@ public final class PRNGFixes { */ public static class LinuxPRNGSecureRandom extends SecureRandomSpi { - /* + /* * IMPLEMENTATION NOTE: Requests to generate bytes and to mix in a seed - * are passed through to the Linux PRNG (/dev/urandom). Instances of - * this class seed themselves by mixing in the current time, PID, UID, - * build fingerprint, and hardware serial number (where available) into - * Linux PRNG. - * - * Concurrency: Read requests to the underlying Linux PRNG are - * serialized (on sLock) to ensure that multiple threads do not get - * duplicated PRNG output. - */ + * are passed through to the Linux PRNG (/dev/urandom). Instances of + * this class seed themselves by mixing in the current time, PID, UID, + * build fingerprint, and hardware serial number (where available) into + * Linux PRNG. + * + * Concurrency: Read requests to the underlying Linux PRNG are + * serialized (on sLock) to ensure that multiple threads do not get + * duplicated PRNG output. + */ private static final File URANDOM_FILE = new File("/dev/urandom"); @@ -271,56 +323,4 @@ public final class PRNGFixes { } } } - - /** - * Generates a device- and invocation-specific seed to be mixed into the - * Linux PRNG. - */ - private static byte[] generateSeed() { - try { - ByteArrayOutputStream seedBuffer = new ByteArrayOutputStream(); - DataOutputStream seedBufferOut = new DataOutputStream(seedBuffer); - seedBufferOut.writeLong(System.currentTimeMillis()); - seedBufferOut.writeLong(System.nanoTime()); - seedBufferOut.writeInt(Process.myPid()); - seedBufferOut.writeInt(Process.myUid()); - seedBufferOut.write(BUILD_FINGERPRINT_AND_DEVICE_SERIAL); - seedBufferOut.close(); - return seedBuffer.toByteArray(); - } catch (IOException e) { - throw new SecurityException("Failed to generate seed", e); - } - } - - /** - * Gets the hardware serial number of this device. - * - * @return serial number or {@code null} if not available. - */ - private static String getDeviceSerialNumber() { - // We're using the Reflection API because Build.SERIAL is only available - // since API Level 9 (Gingerbread, Android 2.3). - try { - return (String) Build.class.getField("SERIAL").get(null); - } catch (Exception ignored) { - return null; - } - } - - private static byte[] getBuildFingerprintAndDeviceSerial() { - StringBuilder result = new StringBuilder(); - String fingerprint = Build.FINGERPRINT; - if (fingerprint != null) { - result.append(fingerprint); - } - String serial = getDeviceSerialNumber(); - if (serial != null) { - result.append(serial); - } - try { - return result.toString().getBytes("UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new RuntimeException("UTF-8 encoding not supported"); - } - } }
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/utils/Resolver.java b/src/main/java/de/pixart/messenger/utils/Resolver.java index e159c3dd0..7817e7914 100644 --- a/src/main/java/de/pixart/messenger/utils/Resolver.java +++ b/src/main/java/de/pixart/messenger/utils/Resolver.java @@ -63,14 +63,26 @@ public class Resolver { final Field useHardcodedDnsServers = DNSClient.class.getDeclaredField("useHardcodedDnsServers"); useHardcodedDnsServers.setAccessible(true); useHardcodedDnsServers.setBoolean(dnsClient, false); - } catch (NoSuchFieldException e) { - Log.e(Config.LOGTAG, "Unable to disable hardcoded DNS servers", e); - } catch (IllegalAccessException e) { + } catch (NoSuchFieldException | IllegalAccessException e) { Log.e(Config.LOGTAG, "Unable to disable hardcoded DNS servers", e); } } + public static List<Result> fromHardCoded(String hostname, int port) { + Result result = new Result(); + result.hostname = DNSName.from(hostname); + result.port = port; + result.directTls = port == 443 || port == 5223; + result.authenticated = true; + return Collections.singletonList(result); + } + + public static List<Result> resolve(String domain) { + final List<Result> ipResults = fromIpAddress(domain); + if (ipResults.size() > 0) { + return ipResults; + } final List<Result> results = new ArrayList<>(); final List<Result> fallbackResults = new ArrayList<>(); Thread[] threads = new Thread[3]; @@ -125,9 +137,21 @@ public class Resolver { for (Thread thread : threads) { thread.interrupt(); } - synchronized (results) { - return new ArrayList<>(results); - } + return Collections.emptyList(); + } + } + + private static List<Result> fromIpAddress(String domain) { + if (!IP.matches(domain)) { + return Collections.emptyList(); + } + try { + Result result = new Result(); + result.ip = InetAddress.getByName(domain); + result.port = 5222; + return Collections.singletonList(result); + } catch (UnknownHostException e) { + return Collections.emptyList(); } } @@ -277,7 +301,8 @@ public class Resolver { } catch (UnknownHostException e) { result.ip = null; } - result.hostname = DNSName.from(cursor.getString(cursor.getColumnIndex(HOSTNAME))); + final String hostname = cursor.getString(cursor.getColumnIndex(HOSTNAME)); + result.hostname = hostname == null ? null : DNSName.from(hostname); result.port = cursor.getInt(cursor.getColumnIndex(PORT)); result.priority = cursor.getInt(cursor.getColumnIndex(PRIORITY)); result.authenticated = cursor.getInt(cursor.getColumnIndex(AUTHENTICATED)) > 0; @@ -369,7 +394,7 @@ public class Resolver { public ContentValues toContentValues() { final ContentValues contentValues = new ContentValues(); contentValues.put(IP, ip == null ? null : ip.getAddress()); - contentValues.put(HOSTNAME, hostname.toString()); + contentValues.put(HOSTNAME, hostname == null ? null : hostname.toString()); contentValues.put(PORT, port); contentValues.put(PRIORITY, priority); contentValues.put(DIRECT_TLS, directTls ? 1 : 0); @@ -377,5 +402,4 @@ public class Resolver { return contentValues; } } - }
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/utils/SSLSocketHelper.java b/src/main/java/de/pixart/messenger/utils/SSLSocketHelper.java index ad3629354..ce8aa009f 100644 --- a/src/main/java/de/pixart/messenger/utils/SSLSocketHelper.java +++ b/src/main/java/de/pixart/messenger/utils/SSLSocketHelper.java @@ -1,20 +1,30 @@ package de.pixart.messenger.utils; import android.os.Build; +import android.support.annotation.RequiresApi; +import android.util.Log; + +import org.conscrypt.Conscrypt; import java.lang.reflect.Method; import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.LinkedList; +import javax.net.ssl.SNIHostName; import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; -import javax.net.ssl.SSLSocketFactory; + +import de.pixart.messenger.Config; +import de.pixart.messenger.entities.Account; public class SSLSocketHelper { - public static void setSecurity(final SSLSocket sslSocket) throws NoSuchAlgorithmException { + public static void setSecurity(final SSLSocket sslSocket) { final String[] supportProtocols; final Collection<String> supportedProtocols = new LinkedList<>( Arrays.asList(sslSocket.getSupportedProtocols())); @@ -30,44 +40,65 @@ public class SSLSocketHelper { } } - public static void setSNIHost(final SSLSocketFactory factory, final SSLSocket socket, final String hostname) { - if (factory instanceof android.net.SSLCertificateSocketFactory && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) { - ((android.net.SSLCertificateSocketFactory) factory).setHostname(socket, hostname); + public static void setHostname(final SSLSocket socket, final String hostname) { + if (Conscrypt.isConscrypt(socket)) { + Conscrypt.setHostname(socket, hostname); + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + setHostnameNougat(socket, hostname); } else { - try { - socket.getClass().getMethod("setHostname", String.class).invoke(socket, hostname); - } catch (Throwable e) { - // ignore any error, we just can't set the hostname... - } + setHostnameReflection(socket, hostname); } } - public static void setAlpnProtocol(final SSLSocketFactory factory, final SSLSocket socket, final String protocol) { + private static void setHostnameReflection(final SSLSocket socket, final String hostname) { + try { + socket.getClass().getMethod("setHostname", String.class).invoke(socket, hostname); + } catch (Throwable e) { + Log.e(Config.LOGTAG, "unable to set SNI name on socket (" + hostname + ")", e); + } + } + + + @RequiresApi(api = Build.VERSION_CODES.N) + private static void setHostnameNougat(final SSLSocket socket, final String hostname) { + final SSLParameters parameters = new SSLParameters(); + parameters.setServerNames(Collections.singletonList(new SNIHostName(hostname))); + socket.setSSLParameters(parameters); + } + + private static void setApplicationProtocolReflection(final SSLSocket socket, final String protocol) { try { - if (factory instanceof android.net.SSLCertificateSocketFactory && android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { - // can't call directly because of @hide? - //((android.net.SSLCertificateSocketFactory)factory).setAlpnProtocols(new byte[][]{protocol.getBytes("UTF-8")}); - android.net.SSLCertificateSocketFactory.class.getMethod("setAlpnProtocols", byte[][].class).invoke(socket, new Object[]{new byte[][]{protocol.getBytes("UTF-8")}}); - } else { - final Method method = socket.getClass().getMethod("setAlpnProtocols", byte[].class); - // the concatenation of 8-bit, length prefixed protocol names, just one in our case... - // http://tools.ietf.org/html/draft-agl-tls-nextprotoneg-04#page-4 - final byte[] protocolUTF8Bytes = protocol.getBytes("UTF-8"); - final byte[] lengthPrefixedProtocols = new byte[protocolUTF8Bytes.length + 1]; - lengthPrefixedProtocols[0] = (byte) protocol.length(); // cannot be over 255 anyhow - System.arraycopy(protocolUTF8Bytes, 0, lengthPrefixedProtocols, 1, protocolUTF8Bytes.length); - method.invoke(socket, new Object[]{lengthPrefixedProtocols}); - } + final Method method = socket.getClass().getMethod("setAlpnProtocols", byte[].class); + // the concatenation of 8-bit, length prefixed protocol names, just one in our case... + // http://tools.ietf.org/html/draft-agl-tls-nextprotoneg-04#page-4 + final byte[] protocolUTF8Bytes = protocol.getBytes("UTF-8"); + final byte[] lengthPrefixedProtocols = new byte[protocolUTF8Bytes.length + 1]; + lengthPrefixedProtocols[0] = (byte) protocol.length(); // cannot be over 255 anyhow + System.arraycopy(protocolUTF8Bytes, 0, lengthPrefixedProtocols, 1, protocolUTF8Bytes.length); + method.invoke(socket, new Object[]{lengthPrefixedProtocols}); } catch (Throwable e) { - // ignore any error, we just can't set the alpn protocol... + Log.e(Config.LOGTAG, "unable to set ALPN on socket", e); + } + } + + public static void setApplicationProtocol(final SSLSocket socket, final String protocol) { + if (Conscrypt.isConscrypt(socket)) { + Conscrypt.setApplicationProtocols(socket, new String[]{protocol}); + } else { + setApplicationProtocolReflection(socket, protocol); } } public static SSLContext getSSLContext() throws NoSuchAlgorithmException { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + try { + return SSLContext.getInstance("TLSv1.3"); + } catch (NoSuchAlgorithmException e) { return SSLContext.getInstance("TLSv1.2"); - } else { - return SSLContext.getInstance("TLS"); } } -} + + public static void log(Account account, SSLSocket socket) { + SSLSession session = socket.getSession(); + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": protocol=" + session.getProtocol() + " cipher=" + session.getCipherSuite()); + } +}
\ No newline at end of file diff --git a/src/main/java/de/pixart/messenger/utils/TLSSocketFactory.java b/src/main/java/de/pixart/messenger/utils/TLSSocketFactory.java index cfefbd93d..6daa9ac9b 100644 --- a/src/main/java/de/pixart/messenger/utils/TLSSocketFactory.java +++ b/src/main/java/de/pixart/messenger/utils/TLSSocketFactory.java @@ -17,11 +17,18 @@ public class TLSSocketFactory extends SSLSocketFactory { private final SSLSocketFactory internalSSLSocketFactory; public TLSSocketFactory(X509TrustManager[] trustManager, SecureRandom random) throws KeyManagementException, NoSuchAlgorithmException { - SSLContext context = SSLContext.getInstance("TLS"); + SSLContext context = SSLSocketHelper.getSSLContext(); context.init(null, trustManager, random); this.internalSSLSocketFactory = context.getSocketFactory(); } + private static Socket enableTLSOnSocket(Socket socket) { + if (socket instanceof SSLSocket) { + SSLSocketHelper.setSecurity((SSLSocket) socket); + } + return socket; + } + @Override public String[] getDefaultCipherSuites() { return CryptoHelper.getOrderedCipherSuites(internalSSLSocketFactory.getDefaultCipherSuites()); @@ -56,15 +63,4 @@ public class TLSSocketFactory extends SSLSocketFactory { 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/de/pixart/messenger/utils/XmppUri.java b/src/main/java/de/pixart/messenger/utils/XmppUri.java index db0002a29..a44c9a174 100644 --- a/src/main/java/de/pixart/messenger/utils/XmppUri.java +++ b/src/main/java/de/pixart/messenger/utils/XmppUri.java @@ -22,7 +22,7 @@ public class XmppUri { private String name; private String action; private boolean safeSource = true; - private static final String OMEMO_URI_PARAM = "omemo-sid-"; + public static final String OMEMO_URI_PARAM = "omemo-sid-"; private static final String OTR_URI_PARAM = "otr-fingerprint"; public static final String ACTION_JOIN = "join"; public static final String ACTION_MESSAGE = "message"; diff --git a/src/main/java/de/pixart/messenger/xml/XmlReader.java b/src/main/java/de/pixart/messenger/xml/XmlReader.java index 703f1239c..a67442f49 100644 --- a/src/main/java/de/pixart/messenger/xml/XmlReader.java +++ b/src/main/java/de/pixart/messenger/xml/XmlReader.java @@ -48,7 +48,7 @@ public class XmlReader { } } - public Tag readTag() throws XmlPullParserException, IOException { + public Tag readTag() throws IOException { try { while (this.is != null && parser.next() != XmlPullParser.END_DOCUMENT) { if (parser.getEventType() == XmlPullParser.START_TAG) { diff --git a/src/main/java/de/pixart/messenger/xmpp/XmppConnection.java b/src/main/java/de/pixart/messenger/xmpp/XmppConnection.java index cbfdbc365..73cfbab86 100644 --- a/src/main/java/de/pixart/messenger/xmpp/XmppConnection.java +++ b/src/main/java/de/pixart/messenger/xmpp/XmppConnection.java @@ -48,7 +48,6 @@ import java.util.regex.Matcher; import javax.net.ssl.KeyManager; import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.X509KeyManager; @@ -77,7 +76,6 @@ import de.pixart.messenger.services.NotificationService; import de.pixart.messenger.services.XmppConnectionService; import de.pixart.messenger.ui.EditAccountActivity; import de.pixart.messenger.utils.CryptoHelper; -import de.pixart.messenger.utils.IP; import de.pixart.messenger.utils.Namespace; import de.pixart.messenger.utils.Patterns; import de.pixart.messenger.utils.Resolver; @@ -210,7 +208,7 @@ public class XmppConnection implements Runnable { } } - protected void changeStatus(final Account.State nextStatus) { + private void changeStatus(final Account.State nextStatus) { synchronized (this) { if (Thread.currentThread().isInterrupted()) { Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": not changing status to " + nextStatus + " because thread was interrupted"); @@ -304,69 +302,32 @@ public class XmppConnection implements Runnable { } catch (Exception e) { throw new IOException(e.getMessage()); } - } else if (extended && !account.getHostname().isEmpty()) { - - this.verifiedHostname = account.getHostname(); - - try { - InetSocketAddress address = new InetSocketAddress(this.verifiedHostname, account.getPort()); - features.encryptionEnabled = address.getPort() == 5223; - if (features.encryptionEnabled) { - try { - final TlsFactoryVerifier tlsFactoryVerifier = getTlsFactoryVerifier(); - localSocket = tlsFactoryVerifier.factory.createSocket(); - localSocket.connect(address, Config.SOCKET_TIMEOUT * 1000); - final SSLSession session = ((SSLSocket) localSocket).getSession(); - final String domain = account.getJid().getDomain(); - if (!tlsFactoryVerifier.verifier.verify(domain, this.verifiedHostname, session)) { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": TLS certificate verification failed"); - throw new StateChangingException(Account.State.TLS_ERROR); - } - } catch (KeyManagementException e) { - throw new StateChangingException(Account.State.TLS_ERROR); - } - } else { - localSocket = new Socket(); - localSocket.connect(address, Config.SOCKET_TIMEOUT * 1000); - } - } catch (IOException | IllegalArgumentException e) { - throw new UnknownHostException(); + } else { + final String domain = account.getJid().getDomain(); + final List<Resolver.Result> results; + final boolean hardcoded = extended && !account.getHostname().isEmpty(); + if (hardcoded) { + results = Resolver.fromHardCoded(account.getHostname(), account.getPort()); + } else { + results = Resolver.resolve(domain); } - try { - startXmpp(localSocket); - } catch (InterruptedException e) { - Log.d(Config.LOGTAG,account.getJid().asBareJid()+": thread was interrupted before beginning stream"); + if (Thread.currentThread().isInterrupted()) { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": Thread was interrupted"); return; - } catch (Exception e) { - throw new IOException(e.getMessage()); } - } else if (IP.matches(account.getServer())) { - localSocket = new Socket(); - try { - localSocket.connect(new InetSocketAddress(account.getServer(), 5222), Config.SOCKET_TIMEOUT * 1000); - } catch (IOException e) { - throw new UnknownHostException(); - } - try { - startXmpp(localSocket); - } catch (InterruptedException e) { - Log.d(Config.LOGTAG,account.getJid().asBareJid()+": thread was interrupted before beginning stream"); + if (results.size() == 0) { + Log.e(Config.LOGTAG, account.getJid().asBareJid() + ": Resolver results were empty"); return; - } catch (Exception e) { - throw new IOException(e.getMessage()); } - } else { - final String domain = account.getJid().getDomain(); - List<Resolver.Result> results = Resolver.resolve(account.getJid().getDomain()); - Resolver.Result storedBackupResult; - if (!Thread.currentThread().isInterrupted()) { + final Resolver.Result storedBackupResult; + if (hardcoded) { + storedBackupResult = null; + } else { storedBackupResult = mXmppConnectionService.databaseBackend.findResolverResult(domain); if (storedBackupResult != null && !results.contains(storedBackupResult)) { results.add(storedBackupResult); Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": loaded backup resolver result from db: " + storedBackupResult); } - } else { - storedBackupResult = null; } for (Iterator<Resolver.Result> iterator = results.iterator(); iterator.hasNext(); ) { final Resolver.Result result = iterator.next(); @@ -378,16 +339,17 @@ public class XmppConnection implements Runnable { // if tls is true, encryption is implied and must not be started features.encryptionEnabled = result.isDirectTls(); verifiedHostname = result.isAuthenticated() ? result.getHostname().toString() : null; + Log.d(Config.LOGTAG, "verified hostname " + verifiedHostname); final InetSocketAddress addr; if (result.getIp() != null) { addr = new InetSocketAddress(result.getIp(), result.getPort()); Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() - + ": using values from dns " + result.getHostname().toString() - + "/" + result.getIp().getHostAddress() + ":" + result.getPort() + " tls: " + features.encryptionEnabled); + + ": using values from resolver " + (result.getHostname() == null ? "" : result.getHostname().toString() + + "/") + result.getIp().getHostAddress() + ":" + result.getPort() + " tls: " + features.encryptionEnabled); } else { addr = new InetSocketAddress(IDN.toASCII(result.getHostname().toString()), result.getPort()); Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() - + ": using values from dns " + + ": using values from resolver " + result.getHostname().toString() + ":" + result.getPort() + " tls: " + features.encryptionEnabled); } @@ -403,8 +365,8 @@ public class XmppConnection implements Runnable { } SSLSocketHelper.setSecurity((SSLSocket) localSocket); - SSLSocketHelper.setSNIHost(tlsFactoryVerifier.factory, (SSLSocket) localSocket, account.getServer()); - SSLSocketHelper.setAlpnProtocol(tlsFactoryVerifier.factory, (SSLSocket) localSocket, "xmpp-client"); + SSLSocketHelper.setHostname((SSLSocket) localSocket, account.getServer()); + SSLSocketHelper.setApplicationProtocol((SSLSocket) localSocket, "xmpp-client"); localSocket.connect(addr, Config.SOCKET_TIMEOUT * 1000); @@ -415,14 +377,18 @@ public class XmppConnection implements Runnable { } } } - + localSocket.setSoTimeout(Config.SOCKET_TIMEOUT * 1000); if (startXmpp(localSocket)) { - if (!result.equals(storedBackupResult)) { + localSocket.setSoTimeout(0); //reset to 0; once the connection is established we don’t want this + if (!hardcoded && !result.equals(storedBackupResult)) { mXmppConnectionService.databaseBackend.saveResolverResult(domain, result); } break; // successfully connected to server that speaks xmpp } else { localSocket.close(); + if (!iterator.hasNext()) { + throw new StateChangingException(Account.State.STREAM_OPENING_ERROR); + } } } catch (final StateChangingException e) { throw e; @@ -446,7 +412,7 @@ public class XmppConnection implements Runnable { this.changeStatus(Account.State.SERVER_NOT_FOUND); } catch (final SocksSocketFactory.SocksProxyNotFoundException e) { this.changeStatus(Account.State.TOR_NOT_AVAILABLE); - } catch (final IOException | XmlPullParserException | NoSuchAlgorithmException e) { + } catch (final IOException | XmlPullParserException e) { Log.d(Config.LOGTAG, account.getJid().asBareJid().toString() + ": " + e.getMessage()); this.changeStatus(Account.State.OFFLINE); this.attempt = Math.max(0, this.attempt - 1); @@ -482,6 +448,9 @@ public class XmppConnection implements Runnable { if (Thread.currentThread().isInterrupted()) { throw new InterruptedException(); } + if (socket instanceof SSLSocket) { + SSLSocketHelper.log(account, (SSLSocket) socket); + } return tag != null && tag.isStart("stream"); } @@ -524,7 +493,7 @@ public class XmppConnection implements Runnable { } else if (nextTag.isStart("features")) { processStreamFeatures(nextTag); } else if (nextTag.isStart("proceed")) { - switchOverToTls(nextTag); + switchOverToTls(); } else if (nextTag.isStart("success")) { final String challenge = tagReader.readElement(nextTag).getContent(); try { @@ -542,7 +511,7 @@ public class XmppConnection implements Runnable { if (tag != null && tag.isStart("stream")) { processStream(); } else { - throw new IOException("server didn't restart stream after successful auth"); + throw new StateChangingException(Account.State.STREAM_OPENING_ERROR); } break; } else if (nextTag.isStart("failure")) { @@ -852,7 +821,7 @@ public class XmppConnection implements Runnable { tagWriter.writeTag(startTLS); } - private void switchOverToTls(final Tag currentTag) throws XmlPullParserException, IOException { + private void switchOverToTls() throws XmlPullParserException, IOException { tagReader.readTag(); try { final TlsFactoryVerifier tlsFactoryVerifier = getTlsFactoryVerifier(); @@ -869,6 +838,8 @@ public class XmppConnection implements Runnable { } SSLSocketHelper.setSecurity(sslSocket); + SSLSocketHelper.setHostname(sslSocket, account.getServer()); + SSLSocketHelper.setApplicationProtocol(sslSocket, "xmpp-client"); if (!tlsFactoryVerifier.verifier.verify(account.getServer(), this.verifiedHostname, sslSocket.getSession())) { Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": TLS certificate verification failed"); @@ -881,9 +852,10 @@ public class XmppConnection implements Runnable { features.encryptionEnabled = true; final Tag tag = tagReader.readTag(); if (tag != null && tag.isStart("stream")) { + SSLSocketHelper.log(account, sslSocket); processStream(); } else { - throw new IOException("server didn't restart stream after STARTTLS"); + throw new StateChangingException(Account.State.STREAM_OPENING_ERROR); } sslSocket.close(); } catch (final NoSuchAlgorithmException | KeyManagementException e1) { diff --git a/src/main/java/de/pixart/messenger/xmpp/jingle/JingleConnection.java b/src/main/java/de/pixart/messenger/xmpp/jingle/JingleConnection.java index 6c726c8b9..2f3ec09ea 100644 --- a/src/main/java/de/pixart/messenger/xmpp/jingle/JingleConnection.java +++ b/src/main/java/de/pixart/messenger/xmpp/jingle/JingleConnection.java @@ -2,8 +2,8 @@ package de.pixart.messenger.xmpp.jingle; import android.util.Base64; import android.util.Log; -import android.util.Pair; +import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; @@ -84,6 +84,7 @@ public class JingleConnection implements Transferable { private boolean sentCandidate = false; private boolean acceptedAutomatically = false; + private boolean cancelled = false; private XmppAxolotlMessage mXmppAxolotlMessage; @@ -92,13 +93,9 @@ public class JingleConnection implements Transferable { private OutputStream mFileOutputStream; private InputStream mFileInputStream; - private OnIqPacketReceived responseListener = new OnIqPacketReceived() { - - @Override - public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() != IqPacket.TYPE.RESULT) { - fail(IqParser.extractErrorMessage(packet)); - } + private OnIqPacketReceived responseListener = (account, packet) -> { + if (packet.getType() != IqPacket.TYPE.RESULT) { + fail(IqParser.extractErrorMessage(packet)); } }; private byte[] expectedHash = new byte[0]; @@ -489,36 +486,32 @@ public class JingleConnection implements Transferable { if (message.getType() == Message.TYPE_IMAGE || message.getType() == Message.TYPE_FILE) { content.setTransportId(this.transportId); this.file = this.mXmppConnectionService.getFileBackend().getFile(message, false); - Pair<InputStream, Integer> pair; - try { - if (message.getEncryption() == Message.ENCRYPTION_OTR) { - Conversational conversation = this.message.getConversation(); - if (!this.mXmppConnectionService.renewSymmetricKey((Conversation) conversation)) { - Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": could not set symmetric key"); - cancel(); - } - Conversation c = (Conversation) this.message.getConversation(); - this.file.setKeyAndIv(c.getSymmetricKey()); - pair = AbstractConnectionManager.createInputStream(this.file, false); - this.file.setExpectedSize(pair.second); - content.setFileOffer(this.file, true, this.ftVersion); - } else if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) { - this.file.setKey(mXmppAxolotlMessage.getInnerKey()); - this.file.setIv(mXmppAxolotlMessage.getIV()); - pair = AbstractConnectionManager.createInputStream(this.file, true); - this.file.setExpectedSize(pair.second); - content.setFileOffer(this.file, false, this.ftVersion).addChild(mXmppAxolotlMessage.toElement()); - } else { - pair = AbstractConnectionManager.createInputStream(this.file, false); - this.file.setExpectedSize(pair.second); - content.setFileOffer(this.file, false, this.ftVersion); + if (message.getEncryption() == Message.ENCRYPTION_OTR) { + Conversational conversation = this.message.getConversation(); + if (!this.mXmppConnectionService.renewSymmetricKey((Conversation) conversation)) { + Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": could not set symmetric key"); + cancel(); } + Conversation c = (Conversation) this.message.getConversation(); + this.file.setKeyAndIv(c.getSymmetricKey()); + this.file.setExpectedSize((file.getSize() / 16 + 1) * 16); + content.setFileOffer(this.file, true, this.ftVersion); + } else if (message.getEncryption() == Message.ENCRYPTION_AXOLOTL) { + this.file.setKey(mXmppAxolotlMessage.getInnerKey()); + this.file.setIv(mXmppAxolotlMessage.getIV()); + this.file.setExpectedSize(file.getSize() + 16); + content.setFileOffer(this.file, false, this.ftVersion).addChild(mXmppAxolotlMessage.toElement()); + } else { + this.file.setExpectedSize(file.getSize()); + content.setFileOffer(this.file, false, this.ftVersion); + } + message.resetFileParams(); + try { + this.mFileInputStream = new FileInputStream(file); } catch (FileNotFoundException e) { - cancel(); + abort(); return; } - message.resetFileParams(); - this.mFileInputStream = pair.first; content.setTransportId(this.transportId); content.socks5transport().setChildren(getCandidatesAsElements()); packet.setContent(content); @@ -893,7 +886,7 @@ public class JingleConnection implements Transferable { this.mJingleStatus = JINGLE_STATUS_FINISHED; this.mXmppConnectionService.markMessage(this.message, Message.STATUS_SEND_RECEIVED); this.disconnectSocks5Connections(); - if (this.transport != null && this.transport instanceof JingleInbandTransport) { + if (this.transport instanceof JingleInbandTransport) { this.transport.disconnect(); } this.message.setTransferable(null); @@ -904,8 +897,13 @@ public class JingleConnection implements Transferable { } public void cancel() { + this.cancelled = true; + abort(); + } + + public void abort() { this.disconnectSocks5Connections(); - if (this.transport != null && this.transport instanceof JingleInbandTransport) { + if (this.transport instanceof JingleInbandTransport) { this.transport.disconnect(); } this.sendCancel(); @@ -917,7 +915,7 @@ public class JingleConnection implements Transferable { } this.mJingleConnectionManager.updateConversationUi(true); } else { - this.mXmppConnectionService.markMessage(this.message, Message.STATUS_SEND_FAILED); + this.mXmppConnectionService.markMessage(this.message, Message.STATUS_SEND_FAILED, cancelled ? Message.ERROR_MESSAGE_CANCELLED : null); this.message.setTransferable(null); } } @@ -929,7 +927,7 @@ public class JingleConnection implements Transferable { private void fail(String errorMessage) { this.mJingleStatus = JINGLE_STATUS_FAILED; this.disconnectSocks5Connections(); - if (this.transport != null && this.transport instanceof JingleInbandTransport) { + if (this.transport instanceof JingleInbandTransport) { this.transport.disconnect(); } FileBackend.close(mFileInputStream); @@ -944,7 +942,7 @@ public class JingleConnection implements Transferable { } else { this.mXmppConnectionService.markMessage(this.message, Message.STATUS_SEND_FAILED, - errorMessage); + cancelled ? Message.ERROR_MESSAGE_CANCELLED : errorMessage); this.message.setTransferable(null); } } diff --git a/src/main/java/de/pixart/messenger/xmpp/jingle/JingleConnectionManager.java b/src/main/java/de/pixart/messenger/xmpp/jingle/JingleConnectionManager.java index de0ce1ea0..bef7a000d 100644 --- a/src/main/java/de/pixart/messenger/xmpp/jingle/JingleConnectionManager.java +++ b/src/main/java/de/pixart/messenger/xmpp/jingle/JingleConnectionManager.java @@ -167,7 +167,7 @@ public class JingleConnectionManager extends AbstractConnectionManager { public void cancelInTransmission() { for (JingleConnection connection : this.connections) { if (connection.getJingleStatus() == JingleConnection.JINGLE_STATUS_TRANSMITTING) { - connection.cancel(); + connection.abort(); } } } diff --git a/src/main/java/de/pixart/messenger/xmpp/jingle/JingleInbandTransport.java b/src/main/java/de/pixart/messenger/xmpp/jingle/JingleInbandTransport.java index 151e9409f..df8b962c4 100644 --- a/src/main/java/de/pixart/messenger/xmpp/jingle/JingleInbandTransport.java +++ b/src/main/java/de/pixart/messenger/xmpp/jingle/JingleInbandTransport.java @@ -14,6 +14,7 @@ import de.pixart.messenger.Config; import de.pixart.messenger.entities.Account; import de.pixart.messenger.entities.DownloadableFile; import de.pixart.messenger.persistance.FileBackend; +import de.pixart.messenger.services.AbstractConnectionManager; import de.pixart.messenger.xml.Element; import de.pixart.messenger.xmpp.OnIqPacketReceived; import de.pixart.messenger.xmpp.stanzas.IqPacket; @@ -35,6 +36,7 @@ public class JingleInbandTransport extends JingleTransport { private JingleConnection connection; private InputStream fileInputStream = null; + private InputStream innerInputStream = null; private OutputStream fileOutputStream = null; private long remainingSize = 0; private long fileSize = 0; @@ -129,10 +131,11 @@ public class JingleInbandTransport extends JingleTransport { callback.onFileTransferAborted(); return; } + innerInputStream = AbstractConnectionManager.upgrade(file, fileInputStream, false); if (this.connected) { this.sendNextBlock(); } - } catch (NoSuchAlgorithmException e) { + } catch (Exception e) { callback.onFileTransferAborted(); Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": " + e.getMessage()); } @@ -141,26 +144,14 @@ public class JingleInbandTransport extends JingleTransport { @Override public void disconnect() { this.connected = false; - if (this.fileOutputStream != null) { - try { - this.fileOutputStream.close(); - } catch (IOException e) { - - } - } - if (this.fileInputStream != null) { - try { - this.fileInputStream.close(); - } catch (IOException e) { - - } - } + FileBackend.close(fileOutputStream); + FileBackend.close(fileInputStream); } private void sendNextBlock() { byte[] buffer = new byte[this.blockSize]; try { - int count = fileInputStream.read(buffer); + int count = innerInputStream.read(buffer); if (count == -1) { sendClose(); file.setSha1Sum(digest.digest()); @@ -168,7 +159,7 @@ public class JingleInbandTransport extends JingleTransport { fileInputStream.close(); return; } else if (count != buffer.length) { - int rem = fileInputStream.read(buffer, count, buffer.length - count); + int rem = innerInputStream.read(buffer, count, buffer.length - count); if (rem > 0) { count += rem; } diff --git a/src/main/java/de/pixart/messenger/xmpp/jingle/JingleSocks5Transport.java b/src/main/java/de/pixart/messenger/xmpp/jingle/JingleSocks5Transport.java index 374e02750..35498664f 100644 --- a/src/main/java/de/pixart/messenger/xmpp/jingle/JingleSocks5Transport.java +++ b/src/main/java/de/pixart/messenger/xmpp/jingle/JingleSocks5Transport.java @@ -15,6 +15,7 @@ import java.security.NoSuchAlgorithmException; import de.pixart.messenger.Config; import de.pixart.messenger.entities.DownloadableFile; import de.pixart.messenger.persistance.FileBackend; +import de.pixart.messenger.services.AbstractConnectionManager; import de.pixart.messenger.utils.CryptoHelper; import de.pixart.messenger.utils.SocksSocketFactory; import de.pixart.messenger.utils.WakeLockHelper; @@ -94,11 +95,12 @@ public class JingleSocks5Transport extends JingleTransport { callback.onFileTransferAborted(); return; } + final InputStream innerInputStream = AbstractConnectionManager.upgrade(file, fileInputStream, false); long size = file.getExpectedSize(); long transmitted = 0; int count; byte[] buffer = new byte[8192]; - while ((count = fileInputStream.read(buffer)) > 0) { + while ((count = innerInputStream.read(buffer)) > 0) { outputStream.write(buffer, 0, count); digest.update(buffer, 0, count); transmitted += count; diff --git a/src/main/res/drawable-hdpi/ic_android_grey600_48dp.png b/src/main/res/drawable-hdpi/ic_android_grey600_48dp.png Binary files differnew file mode 100644 index 000000000..3a39d9cc4 --- /dev/null +++ b/src/main/res/drawable-hdpi/ic_android_grey600_48dp.png diff --git a/src/main/res/drawable-hdpi/ic_delete_black_24dp.png b/src/main/res/drawable-hdpi/ic_delete_black_24dp.png Binary files differnew file mode 100644 index 000000000..789d96d42 --- /dev/null +++ b/src/main/res/drawable-hdpi/ic_delete_black_24dp.png diff --git a/src/main/res/drawable-hdpi/ic_menu_white_24dp.png b/src/main/res/drawable-hdpi/ic_menu_white_24dp.png Binary files differnew file mode 100644 index 000000000..aeb1e31a0 --- /dev/null +++ b/src/main/res/drawable-hdpi/ic_menu_white_24dp.png diff --git a/src/main/res/drawable-hdpi/ic_qrcode_grey600_24dp.png b/src/main/res/drawable-hdpi/ic_qrcode_grey600_24dp.png Binary files differnew file mode 100644 index 000000000..5114bbd0d --- /dev/null +++ b/src/main/res/drawable-hdpi/ic_qrcode_grey600_24dp.png diff --git a/src/main/res/drawable-hdpi/ic_qrcode_scan_white_24dp.png b/src/main/res/drawable-hdpi/ic_qrcode_scan_white_24dp.png Binary files differnew file mode 100644 index 000000000..50aa91fca --- /dev/null +++ b/src/main/res/drawable-hdpi/ic_qrcode_scan_white_24dp.png diff --git a/src/main/res/drawable-hdpi/ic_qrcode_white_24dp.png b/src/main/res/drawable-hdpi/ic_qrcode_white_24dp.png Binary files differnew file mode 100644 index 000000000..cf8d6a8c9 --- /dev/null +++ b/src/main/res/drawable-hdpi/ic_qrcode_white_24dp.png diff --git a/src/main/res/drawable-hdpi/ic_send_attachment_online.png b/src/main/res/drawable-hdpi/ic_send_attachment_online.png Binary files differindex c6c00dfa9..aa35e764d 100644 --- a/src/main/res/drawable-hdpi/ic_send_attachment_online.png +++ b/src/main/res/drawable-hdpi/ic_send_attachment_online.png diff --git a/src/main/res/drawable-hdpi/ic_share_black_24dp.png b/src/main/res/drawable-hdpi/ic_share_black_24dp.png Binary files differnew file mode 100644 index 000000000..7331f79b3 --- /dev/null +++ b/src/main/res/drawable-hdpi/ic_share_black_24dp.png diff --git a/src/main/res/drawable-hdpi/ic_video_black_24dp.png b/src/main/res/drawable-hdpi/ic_video_black_24dp.png Binary files differnew file mode 100644 index 000000000..afd308697 --- /dev/null +++ b/src/main/res/drawable-hdpi/ic_video_black_24dp.png diff --git a/src/main/res/drawable-hdpi/ic_video_white_24dp.png b/src/main/res/drawable-hdpi/ic_video_white_24dp.png Binary files differnew file mode 100644 index 000000000..ca414b356 --- /dev/null +++ b/src/main/res/drawable-hdpi/ic_video_white_24dp.png diff --git a/src/main/res/drawable-mdpi/ic_android_grey600_48dp.png b/src/main/res/drawable-mdpi/ic_android_grey600_48dp.png Binary files differnew file mode 100644 index 000000000..5040f8ef2 --- /dev/null +++ b/src/main/res/drawable-mdpi/ic_android_grey600_48dp.png diff --git a/src/main/res/drawable-mdpi/ic_delete_black_24dp.png b/src/main/res/drawable-mdpi/ic_delete_black_24dp.png Binary files differnew file mode 100644 index 000000000..7981a3def --- /dev/null +++ b/src/main/res/drawable-mdpi/ic_delete_black_24dp.png diff --git a/src/main/res/drawable-mdpi/ic_menu_white_24dp.png b/src/main/res/drawable-mdpi/ic_menu_white_24dp.png Binary files differnew file mode 100644 index 000000000..60e4398a3 --- /dev/null +++ b/src/main/res/drawable-mdpi/ic_menu_white_24dp.png diff --git a/src/main/res/drawable-mdpi/ic_qrcode_grey600_24dp.png b/src/main/res/drawable-mdpi/ic_qrcode_grey600_24dp.png Binary files differnew file mode 100644 index 000000000..24cb66d42 --- /dev/null +++ b/src/main/res/drawable-mdpi/ic_qrcode_grey600_24dp.png diff --git a/src/main/res/drawable-mdpi/ic_qrcode_scan_white_24dp.png b/src/main/res/drawable-mdpi/ic_qrcode_scan_white_24dp.png Binary files differnew file mode 100644 index 000000000..ad5be5cdf --- /dev/null +++ b/src/main/res/drawable-mdpi/ic_qrcode_scan_white_24dp.png diff --git a/src/main/res/drawable-mdpi/ic_qrcode_white_24dp.png b/src/main/res/drawable-mdpi/ic_qrcode_white_24dp.png Binary files differnew file mode 100644 index 000000000..7e28f95dc --- /dev/null +++ b/src/main/res/drawable-mdpi/ic_qrcode_white_24dp.png diff --git a/src/main/res/drawable-mdpi/ic_send_attachment_online.png b/src/main/res/drawable-mdpi/ic_send_attachment_online.png Binary files differindex 7b2157825..921f79ffa 100644 --- a/src/main/res/drawable-mdpi/ic_send_attachment_online.png +++ b/src/main/res/drawable-mdpi/ic_send_attachment_online.png diff --git a/src/main/res/drawable-mdpi/ic_share_black_24dp.png b/src/main/res/drawable-mdpi/ic_share_black_24dp.png Binary files differnew file mode 100644 index 000000000..d4ebebd06 --- /dev/null +++ b/src/main/res/drawable-mdpi/ic_share_black_24dp.png diff --git a/src/main/res/drawable-mdpi/ic_video_black_24dp.png b/src/main/res/drawable-mdpi/ic_video_black_24dp.png Binary files differnew file mode 100644 index 000000000..c4be08a55 --- /dev/null +++ b/src/main/res/drawable-mdpi/ic_video_black_24dp.png diff --git a/src/main/res/drawable-mdpi/ic_video_white_24dp.png b/src/main/res/drawable-mdpi/ic_video_white_24dp.png Binary files differnew file mode 100644 index 000000000..60cc35c42 --- /dev/null +++ b/src/main/res/drawable-mdpi/ic_video_white_24dp.png diff --git a/src/main/res/drawable-xhdpi/ic_android_grey600_48dp.png b/src/main/res/drawable-xhdpi/ic_android_grey600_48dp.png Binary files differnew file mode 100644 index 000000000..eeefeda38 --- /dev/null +++ b/src/main/res/drawable-xhdpi/ic_android_grey600_48dp.png diff --git a/src/main/res/drawable-xhdpi/ic_delete_black_24dp.png b/src/main/res/drawable-xhdpi/ic_delete_black_24dp.png Binary files differnew file mode 100644 index 000000000..16e6e7055 --- /dev/null +++ b/src/main/res/drawable-xhdpi/ic_delete_black_24dp.png diff --git a/src/main/res/drawable-xhdpi/ic_menu_white_24dp.png b/src/main/res/drawable-xhdpi/ic_menu_white_24dp.png Binary files differnew file mode 100644 index 000000000..5b7f7480b --- /dev/null +++ b/src/main/res/drawable-xhdpi/ic_menu_white_24dp.png diff --git a/src/main/res/drawable-xhdpi/ic_qrcode_grey600_24dp.png b/src/main/res/drawable-xhdpi/ic_qrcode_grey600_24dp.png Binary files differnew file mode 100644 index 000000000..3a100a3ed --- /dev/null +++ b/src/main/res/drawable-xhdpi/ic_qrcode_grey600_24dp.png diff --git a/src/main/res/drawable-xhdpi/ic_qrcode_scan_white_24dp.png b/src/main/res/drawable-xhdpi/ic_qrcode_scan_white_24dp.png Binary files differnew file mode 100644 index 000000000..f22df8ea7 --- /dev/null +++ b/src/main/res/drawable-xhdpi/ic_qrcode_scan_white_24dp.png diff --git a/src/main/res/drawable-xhdpi/ic_qrcode_white_24dp.png b/src/main/res/drawable-xhdpi/ic_qrcode_white_24dp.png Binary files differnew file mode 100644 index 000000000..fcb2de2c3 --- /dev/null +++ b/src/main/res/drawable-xhdpi/ic_qrcode_white_24dp.png diff --git a/src/main/res/drawable-xhdpi/ic_send_attachment_online.png b/src/main/res/drawable-xhdpi/ic_send_attachment_online.png Binary files differindex 5ec947bf6..d1ba3d988 100644 --- a/src/main/res/drawable-xhdpi/ic_send_attachment_online.png +++ b/src/main/res/drawable-xhdpi/ic_send_attachment_online.png diff --git a/src/main/res/drawable-xhdpi/ic_share_black_24dp.png b/src/main/res/drawable-xhdpi/ic_share_black_24dp.png Binary files differnew file mode 100644 index 000000000..5c058d4eb --- /dev/null +++ b/src/main/res/drawable-xhdpi/ic_share_black_24dp.png diff --git a/src/main/res/drawable-xhdpi/ic_video_black_24dp.png b/src/main/res/drawable-xhdpi/ic_video_black_24dp.png Binary files differnew file mode 100644 index 000000000..f695100bb --- /dev/null +++ b/src/main/res/drawable-xhdpi/ic_video_black_24dp.png diff --git a/src/main/res/drawable-xhdpi/ic_video_white_24dp.png b/src/main/res/drawable-xhdpi/ic_video_white_24dp.png Binary files differnew file mode 100644 index 000000000..2c764cbb5 --- /dev/null +++ b/src/main/res/drawable-xhdpi/ic_video_white_24dp.png diff --git a/src/main/res/drawable-xxhdpi/ic_android_grey600_48dp.png b/src/main/res/drawable-xxhdpi/ic_android_grey600_48dp.png Binary files differnew file mode 100644 index 000000000..42395613f --- /dev/null +++ b/src/main/res/drawable-xxhdpi/ic_android_grey600_48dp.png diff --git a/src/main/res/drawable-xxhdpi/ic_delete_black_24dp.png b/src/main/res/drawable-xxhdpi/ic_delete_black_24dp.png Binary files differnew file mode 100644 index 000000000..d118ae77b --- /dev/null +++ b/src/main/res/drawable-xxhdpi/ic_delete_black_24dp.png diff --git a/src/main/res/drawable-xxhdpi/ic_menu_white_24dp.png b/src/main/res/drawable-xxhdpi/ic_menu_white_24dp.png Binary files differnew file mode 100644 index 000000000..dcbdc6ac9 --- /dev/null +++ b/src/main/res/drawable-xxhdpi/ic_menu_white_24dp.png diff --git a/src/main/res/drawable-xxhdpi/ic_qrcode_grey600_24dp.png b/src/main/res/drawable-xxhdpi/ic_qrcode_grey600_24dp.png Binary files differnew file mode 100644 index 000000000..1ff70a5c8 --- /dev/null +++ b/src/main/res/drawable-xxhdpi/ic_qrcode_grey600_24dp.png diff --git a/src/main/res/drawable-xxhdpi/ic_qrcode_scan_white_24dp.png b/src/main/res/drawable-xxhdpi/ic_qrcode_scan_white_24dp.png Binary files differnew file mode 100644 index 000000000..796e477ae --- /dev/null +++ b/src/main/res/drawable-xxhdpi/ic_qrcode_scan_white_24dp.png diff --git a/src/main/res/drawable-xxhdpi/ic_qrcode_white_24dp.png b/src/main/res/drawable-xxhdpi/ic_qrcode_white_24dp.png Binary files differnew file mode 100644 index 000000000..2928df951 --- /dev/null +++ b/src/main/res/drawable-xxhdpi/ic_qrcode_white_24dp.png diff --git a/src/main/res/drawable-xxhdpi/ic_send_attachment_online.png b/src/main/res/drawable-xxhdpi/ic_send_attachment_online.png Binary files differindex 34791812f..19e90728f 100644 --- a/src/main/res/drawable-xxhdpi/ic_send_attachment_online.png +++ b/src/main/res/drawable-xxhdpi/ic_send_attachment_online.png diff --git a/src/main/res/drawable-xxhdpi/ic_share_black_24dp.png b/src/main/res/drawable-xxhdpi/ic_share_black_24dp.png Binary files differnew file mode 100644 index 000000000..f52683b7f --- /dev/null +++ b/src/main/res/drawable-xxhdpi/ic_share_black_24dp.png diff --git a/src/main/res/drawable-xxhdpi/ic_video_black_24dp.png b/src/main/res/drawable-xxhdpi/ic_video_black_24dp.png Binary files differnew file mode 100644 index 000000000..2cb46bf1a --- /dev/null +++ b/src/main/res/drawable-xxhdpi/ic_video_black_24dp.png diff --git a/src/main/res/drawable-xxhdpi/ic_video_white_24dp.png b/src/main/res/drawable-xxhdpi/ic_video_white_24dp.png Binary files differnew file mode 100644 index 000000000..77ef5ec76 --- /dev/null +++ b/src/main/res/drawable-xxhdpi/ic_video_white_24dp.png diff --git a/src/main/res/drawable-xxxhdpi/ic_android_grey600_48dp.png b/src/main/res/drawable-xxxhdpi/ic_android_grey600_48dp.png Binary files differnew file mode 100644 index 000000000..7e9d48b6a --- /dev/null +++ b/src/main/res/drawable-xxxhdpi/ic_android_grey600_48dp.png diff --git a/src/main/res/drawable-xxxhdpi/ic_delete_black_24dp.png b/src/main/res/drawable-xxxhdpi/ic_delete_black_24dp.png Binary files differnew file mode 100644 index 000000000..c49da5b4e --- /dev/null +++ b/src/main/res/drawable-xxxhdpi/ic_delete_black_24dp.png diff --git a/src/main/res/drawable-xxxhdpi/ic_menu_white_24dp.png b/src/main/res/drawable-xxxhdpi/ic_menu_white_24dp.png Binary files differnew file mode 100644 index 000000000..0966d1f16 --- /dev/null +++ b/src/main/res/drawable-xxxhdpi/ic_menu_white_24dp.png diff --git a/src/main/res/drawable-xxxhdpi/ic_qrcode_grey600_24dp.png b/src/main/res/drawable-xxxhdpi/ic_qrcode_grey600_24dp.png Binary files differnew file mode 100644 index 000000000..ccd1ac6b7 --- /dev/null +++ b/src/main/res/drawable-xxxhdpi/ic_qrcode_grey600_24dp.png diff --git a/src/main/res/drawable-xxxhdpi/ic_qrcode_scan_white_24dp.png b/src/main/res/drawable-xxxhdpi/ic_qrcode_scan_white_24dp.png Binary files differnew file mode 100644 index 000000000..8b90377f5 --- /dev/null +++ b/src/main/res/drawable-xxxhdpi/ic_qrcode_scan_white_24dp.png diff --git a/src/main/res/drawable-xxxhdpi/ic_qrcode_white_24dp.png b/src/main/res/drawable-xxxhdpi/ic_qrcode_white_24dp.png Binary files differnew file mode 100644 index 000000000..3ec200f7a --- /dev/null +++ b/src/main/res/drawable-xxxhdpi/ic_qrcode_white_24dp.png diff --git a/src/main/res/drawable-xxxhdpi/ic_send_attachment_online.png b/src/main/res/drawable-xxxhdpi/ic_send_attachment_online.png Binary files differindex 50764c450..319709583 100644 --- a/src/main/res/drawable-xxxhdpi/ic_send_attachment_online.png +++ b/src/main/res/drawable-xxxhdpi/ic_send_attachment_online.png diff --git a/src/main/res/drawable-xxxhdpi/ic_share_black_24dp.png b/src/main/res/drawable-xxxhdpi/ic_share_black_24dp.png Binary files differnew file mode 100644 index 000000000..484b6d474 --- /dev/null +++ b/src/main/res/drawable-xxxhdpi/ic_share_black_24dp.png diff --git a/src/main/res/drawable-xxxhdpi/ic_video_black_24dp.png b/src/main/res/drawable-xxxhdpi/ic_video_black_24dp.png Binary files differnew file mode 100644 index 000000000..bd76e9db6 --- /dev/null +++ b/src/main/res/drawable-xxxhdpi/ic_video_black_24dp.png diff --git a/src/main/res/drawable-xxxhdpi/ic_video_white_24dp.png b/src/main/res/drawable-xxxhdpi/ic_video_white_24dp.png Binary files differnew file mode 100644 index 000000000..d8ab46d9f --- /dev/null +++ b/src/main/res/drawable-xxxhdpi/ic_video_white_24dp.png diff --git a/src/main/res/drawable/date_bubble.xml b/src/main/res/drawable/date_bubble.xml index 0ea7b0761..3edf349e9 100644 --- a/src/main/res/drawable/date_bubble.xml +++ b/src/main/res/drawable/date_bubble.xml @@ -11,7 +11,11 @@ android:left="6dp" android:right="6dp" android:top="6dp" /> + <stroke + android:width="1dp" + android:color="@color/grey500"> + </stroke> <solid android:color="@color/lightgreen"> </solid> -</shape> +</shape>
\ No newline at end of file diff --git a/src/main/res/drawable/date_bubble_dark.xml b/src/main/res/drawable/date_bubble_dark.xml index 64f5c1515..345c315c1 100644 --- a/src/main/res/drawable/date_bubble_dark.xml +++ b/src/main/res/drawable/date_bubble_dark.xml @@ -11,7 +11,11 @@ android:left="6dp" android:right="6dp" android:top="6dp" /> + <stroke + android:width="1dp" + android:color="@color/grey800"> + </stroke> <solid android:color="@color/darkgreen"> </solid> -</shape> +</shape>
\ No newline at end of file diff --git a/src/main/res/drawable/message_bubble_received_light.xml b/src/main/res/drawable/message_bubble_received_light.xml index 6619984b3..32e146460 100644 --- a/src/main/res/drawable/message_bubble_received_light.xml +++ b/src/main/res/drawable/message_bubble_received_light.xml @@ -1,29 +1,21 @@ <?xml version="1.0" encoding="utf-8"?> -<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:gravity="top|left"> - <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="36dp" - android:height="24dp" - android:viewportWidth="36" - android:viewportHeight="24" > - <path - android:fillColor="@color/lightwhite" - android:pathData="m2,0 c6,0 10,6 10,10L18,0z" /> - </vector> - </item> - <item android:left="12dp"> - <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> - <corners - android:topLeftRadius="0dp" - android:topRightRadius="5dp" - android:bottomRightRadius="5dp" - android:bottomLeftRadius="5dp"/> - <padding - android:bottom="2dp" - android:left="18dp" - android:right="6dp" - android:top="2dp" /> - <solid android:color="@color/lightwhite" /> - </shape> - </item> -</layer-list> +<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> + <corners + android:topLeftRadius="0dp" + android:topRightRadius="5dp" + android:bottomRightRadius="5dp" + android:bottomLeftRadius="5dp"> + </corners> + <padding + android:bottom="2dp" + android:left="6dp" + android:right="6dp" + android:top="2dp" /> + <stroke + android:width="1dp" + android:color="@color/grey500"> + </stroke> + <solid + android:color="@color/lightwhite"> + </solid> +</shape>
\ No newline at end of file diff --git a/src/main/res/drawable/message_bubble_received_light_dark.xml b/src/main/res/drawable/message_bubble_received_light_dark.xml index 9514bc757..8f9132566 100644 --- a/src/main/res/drawable/message_bubble_received_light_dark.xml +++ b/src/main/res/drawable/message_bubble_received_light_dark.xml @@ -1,29 +1,21 @@ <?xml version="1.0" encoding="utf-8"?> -<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:gravity="top|left"> - <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="36dp" - android:height="24dp" - android:viewportWidth="36" - android:viewportHeight="24" > - <path - android:fillColor="@color/darkwhite" - android:pathData="m2,0 c6,0 10,6 10,10L18,0z" /> - </vector> - </item> - <item android:left="12dp"> - <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> - <corners - android:topLeftRadius="0dp" - android:topRightRadius="5dp" - android:bottomRightRadius="5dp" - android:bottomLeftRadius="5dp" /> - <padding - android:bottom="2dp" - android:left="18dp" - android:right="6dp" - android:top="2dp" /> - <solid android:color="@color/darkwhite" /> - </shape> - </item> -</layer-list> +<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> + <corners + android:topLeftRadius="0dp" + android:topRightRadius="5dp" + android:bottomRightRadius="5dp" + android:bottomLeftRadius="5dp"> + </corners> + <padding + android:bottom="2dp" + android:left="6dp" + android:right="6dp" + android:top="2dp" /> + <stroke + android:width="1dp" + android:color="@color/grey700"> + </stroke> + <solid + android:color="@color/darkwhite"> + </solid> +</shape>
\ No newline at end of file diff --git a/src/main/res/drawable/message_bubble_received_warning.xml b/src/main/res/drawable/message_bubble_received_warning.xml index ae3222a9e..b3bf6d451 100644 --- a/src/main/res/drawable/message_bubble_received_warning.xml +++ b/src/main/res/drawable/message_bubble_received_warning.xml @@ -1,29 +1,21 @@ <?xml version="1.0" encoding="utf-8"?> -<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:gravity="top|left"> - <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="36dp" - android:height="24dp" - android:viewportWidth="36" - android:viewportHeight="24" > - <path - android:fillColor="@color/lightred" - android:pathData="m2,0 c6,0 10,6 10,10L18,0z" /> - </vector> - </item> - <item android:left="12dp"> - <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> - <corners - android:topLeftRadius="0dp" - android:topRightRadius="5dp" - android:bottomRightRadius="5dp" - android:bottomLeftRadius="5dp" /> - <padding - android:bottom="2dp" - android:left="18dp" - android:right="6dp" - android:top="2dp" /> - <solid android:color="@color/lightred" /> - </shape> - </item> -</layer-list> +<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> + <corners + android:topLeftRadius="0dp" + android:topRightRadius="5dp" + android:bottomRightRadius="5dp" + android:bottomLeftRadius="5dp"> + </corners> + <padding + android:bottom="4dp" + android:left="6dp" + android:right="6dp" + android:top="4dp" /> + <stroke + android:width="1dp" + android:color="@color/grey500"> + </stroke> + <solid + android:color="@color/lightred"> + </solid> +</shape>
\ No newline at end of file diff --git a/src/main/res/drawable/message_bubble_received_warning_dark.xml b/src/main/res/drawable/message_bubble_received_warning_dark.xml index 9f8aa779e..5867f67ce 100644 --- a/src/main/res/drawable/message_bubble_received_warning_dark.xml +++ b/src/main/res/drawable/message_bubble_received_warning_dark.xml @@ -1,29 +1,21 @@ <?xml version="1.0" encoding="utf-8"?> -<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:gravity="top|left"> - <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="36dp" - android:height="24dp" - android:viewportWidth="36" - android:viewportHeight="24" > - <path - android:fillColor="@color/darkred" - android:pathData="m2,0 c6,0 10,6 10,10L18,0z" /> - </vector> - </item> - <item android:left="12dp"> - <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> - <corners - android:topLeftRadius="0dp" - android:topRightRadius="5dp" - android:bottomRightRadius="5dp" - android:bottomLeftRadius="5dp" /> - <padding - android:bottom="2dp" - android:left="18dp" - android:right="6dp" - android:top="2dp" /> - <solid android:color="@color/darkred" /> - </shape> - </item> -</layer-list> +<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> + <corners + android:topLeftRadius="0dp" + android:topRightRadius="5dp" + android:bottomRightRadius="5dp" + android:bottomLeftRadius="5dp"> + </corners> + <padding + android:bottom="4dp" + android:left="6dp" + android:right="6dp" + android:top="4dp" /> + <stroke + android:width="1dp" + android:color="@color/grey700"> + </stroke> + <solid + android:color="@color/darkred"> + </solid> +</shape>
\ No newline at end of file diff --git a/src/main/res/drawable/message_bubble_sent_blue.xml b/src/main/res/drawable/message_bubble_sent_blue.xml index 8e519ff6b..1ee3ad0c8 100644 --- a/src/main/res/drawable/message_bubble_sent_blue.xml +++ b/src/main/res/drawable/message_bubble_sent_blue.xml @@ -1,30 +1,21 @@ <?xml version="1.0" encoding="utf-8"?> -<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:gravity="bottom|right"> - <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="36dp" - android:height="24dp" - android:viewportWidth="36" - android:viewportHeight="24" > - <path - android:fillColor="@color/lightblue" - android:pathData="m 34,24 c-6,0 -10,-6 -10,-10 l -6,12 z" /> - - </vector> - </item> - <item android:right="12dp"> - <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> - <corners - android:topLeftRadius="5dp" - android:topRightRadius="5dp" - android:bottomRightRadius="0dp" - android:bottomLeftRadius="5dp" /> - <padding - android:bottom="2dp" - android:left="6dp" - android:right="18dp" - android:top="2dp" /> - <solid android:color="@color/lightblue" /> - </shape> - </item> -</layer-list> +<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> + <corners + android:topLeftRadius="5dp" + android:topRightRadius="5dp" + android:bottomRightRadius="0dp" + android:bottomLeftRadius="5dp"> + </corners> + <padding + android:bottom="4dp" + android:left="6dp" + android:right="6dp" + android:top="4dp" /> + <stroke + android:width="1dp" + android:color="@color/grey500"> + </stroke> + <solid + android:color="@color/lightblue"> + </solid> +</shape>
\ No newline at end of file diff --git a/src/main/res/drawable/message_bubble_sent_blue_dark.xml b/src/main/res/drawable/message_bubble_sent_blue_dark.xml index e6f3c88c1..d9853bf74 100644 --- a/src/main/res/drawable/message_bubble_sent_blue_dark.xml +++ b/src/main/res/drawable/message_bubble_sent_blue_dark.xml @@ -1,29 +1,21 @@ <?xml version="1.0" encoding="utf-8"?> -<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> - <item android:gravity="bottom|right"> - <vector xmlns:android="http://schemas.android.com/apk/res/android" - android:width="36dp" - android:height="24dp" - android:viewportWidth="36" - android:viewportHeight="24" > - <path - android:fillColor="@color/darkblue" - android:pathData="m 34,24 c-6,0 -10,-6 -10,-10 l -6,12 z" /> - </vector> - </item> - <item android:right="12dp"> - <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> - <corners - android:topLeftRadius="5dp" - android:topRightRadius="5dp" - android:bottomRightRadius="0dp" - android:bottomLeftRadius="5dp" /> - <padding - android:bottom="2dp" - android:left="6dp" - android:right="18dp" - android:top="2dp" /> - <solid android:color="@color/darkblue" /> - </shape> - </item> -</layer-list> +<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> + <corners + android:topLeftRadius="5dp" + android:topRightRadius="5dp" + android:bottomRightRadius="0dp" + android:bottomLeftRadius="5dp"> + </corners> + <padding + android:bottom="4dp" + android:left="6dp" + android:right="6dp" + android:top="4dp" /> + <stroke + android:width="1dp" + android:color="@color/grey700"> + </stroke> + <solid + android:color="@color/darkblue"> + </solid> +</shape>
\ No newline at end of file diff --git a/src/main/res/layout/ab_title.xml b/src/main/res/layout/ab_title.xml index a20e9f8bc..79c9d61f7 100644 --- a/src/main/res/layout/ab_title.xml +++ b/src/main/res/layout/ab_title.xml @@ -5,7 +5,7 @@ android:layout_height="match_parent" android:gravity="start|center_vertical"> - <android.support.text.emoji.widget.EmojiTextView + <TextView android:id="@android:id/text1" style="@style/Base.TextAppearance.AppCompat.Widget.ActionBar.Title" android:layout_width="match_parent" @@ -19,7 +19,7 @@ android:onClick="onClick" android:paddingTop="1dp" /> - <android.support.text.emoji.widget.EmojiTextView + <TextView android:id="@android:id/text2" style="@style/Base.TextAppearance.AppCompat.Widget.ActionBar.Subtitle" android:layout_width="match_parent" diff --git a/src/main/res/layout/account_row.xml b/src/main/res/layout/account_row.xml index 9b638172a..e56ca6a95 100644 --- a/src/main/res/layout/account_row.xml +++ b/src/main/res/layout/account_row.xml @@ -15,6 +15,8 @@ android:layout_alignParentLeft="true" android:contentDescription="@string/account_image_description" android:padding="1dp" + app:riv_border_color="?attr/color_border" + app:riv_border_width="1dp" app:riv_corner_radius="@dimen/rounded_image_border" /> <LinearLayout diff --git a/src/main/res/layout/activity_contact_details.xml b/src/main/res/layout/activity_contact_details.xml index 268071fbd..58fe0c901 100644 --- a/src/main/res/layout/activity_contact_details.xml +++ b/src/main/res/layout/activity_contact_details.xml @@ -43,7 +43,7 @@ android:orientation="vertical" android:padding="@dimen/card_padding_regular"> - <android.support.text.emoji.widget.EmojiTextView + <TextView android:id="@+id/contact_display_name" android:layout_width="wrap_content" android:layout_height="wrap_content" @@ -114,7 +114,7 @@ android:layout_marginTop="4dp" android:textAppearance="@style/TextAppearance.Conversations.Subhead" /> - <android.support.text.emoji.widget.EmojiTextView + <TextView android:id="@+id/status_message" android:layout_width="wrap_content" android:layout_height="wrap_content" @@ -124,7 +124,7 @@ android:gravity="center_horizontal" android:textAppearance="@style/TextAppearance.Conversations.Body1" /> - <android.support.text.emoji.widget.EmojiTextView + <TextView android:id="@+id/resource" android:layout_width="wrap_content" android:layout_height="wrap_content" @@ -253,7 +253,7 @@ <android.support.v7.widget.RecyclerView android:id="@+id/media" android:layout_width="match_parent" - android:layout_height="wrap_content" + android:layout_height="100dp" android:layout_marginEnd="-2dp" android:layout_marginStart="-2dp" android:orientation="horizontal" diff --git a/src/main/res/layout/activity_edit_account.xml b/src/main/res/layout/activity_edit_account.xml index 275a1d894..b8c48a382 100644 --- a/src/main/res/layout/activity_edit_account.xml +++ b/src/main/res/layout/activity_edit_account.xml @@ -29,10 +29,10 @@ android:id="@+id/editor" android:layout_width="fill_parent" android:layout_height="wrap_content" - android:layout_marginBottom="@dimen/activity_vertical_margin" android:layout_marginLeft="@dimen/activity_horizontal_margin" - android:layout_marginRight="@dimen/activity_horizontal_margin" android:layout_marginTop="@dimen/activity_vertical_margin" + android:layout_marginRight="@dimen/activity_horizontal_margin" + android:layout_marginBottom="@dimen/activity_vertical_margin" android:orientation="vertical" android:padding="@dimen/card_padding_regular" card_view:cardBackgroundColor="?attr/color_background_secondary"> @@ -51,9 +51,11 @@ android:layout_marginBottom="@dimen/avatar_item_distance" android:adjustViewBounds="true" android:contentDescription="@string/account_image_description" - android:maxHeight="384dp" android:maxWidth="384dp" + android:maxHeight="384dp" android:padding="1dp" + app:riv_border_color="?attr/color_border" + app:riv_border_width="1dp" app:riv_corner_radius="@dimen/rounded_image_border" /> <LinearLayout @@ -177,8 +179,8 @@ android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_gravity="center_horizontal" - android:layout_marginBottom="4dp" android:layout_marginTop="4dp" + android:layout_marginBottom="4dp" android:text="@string/show_privacy" android:visibility="gone" /> @@ -188,8 +190,8 @@ android:layout_height="wrap_content" android:layout_centerHorizontal="true" android:layout_gravity="center_horizontal" - android:layout_marginBottom="4dp" android:layout_marginTop="4dp" + android:layout_marginBottom="4dp" android:text="@string/show_termsofuse" android:visibility="gone" /> </LinearLayout> @@ -200,10 +202,10 @@ android:id="@+id/os_optimization" android:layout_width="fill_parent" android:layout_height="wrap_content" - android:layout_marginBottom="@dimen/activity_vertical_margin" android:layout_marginLeft="@dimen/activity_horizontal_margin" - android:layout_marginRight="@dimen/activity_horizontal_margin" android:layout_marginTop="@dimen/activity_vertical_margin" + android:layout_marginRight="@dimen/activity_horizontal_margin" + android:layout_marginBottom="@dimen/activity_vertical_margin" android:visibility="gone" card_view:cardBackgroundColor="?attr/color_background_secondary"> @@ -258,10 +260,10 @@ android:id="@+id/stats" android:layout_width="fill_parent" android:layout_height="fill_parent" - android:layout_marginBottom="@dimen/activity_vertical_margin" android:layout_marginLeft="@dimen/activity_horizontal_margin" - android:layout_marginRight="@dimen/activity_horizontal_margin" android:layout_marginTop="@dimen/activity_vertical_margin" + android:layout_marginRight="@dimen/activity_horizontal_margin" + android:layout_marginBottom="@dimen/activity_vertical_margin" android:background="@drawable/infocard_border" android:orientation="vertical" android:padding="@dimen/card_padding_regular" @@ -554,9 +556,11 @@ <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_alignParentStart="true" android:layout_alignParentLeft="true" android:layout_centerVertical="true" - android:layout_toLeftOf="@+id/action_copy_to_clipboard" + android:layout_toStartOf="@+id/key_actions" + android:layout_toLeftOf="@+id/key_actions" android:orientation="vertical"> <TextView @@ -574,17 +578,25 @@ </LinearLayout> - <ImageButton - android:id="@+id/action_copy_to_clipboard" + <LinearLayout + android:id="@+id/key_actions" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_alignParentEnd="true" android:layout_alignParentRight="true" android:layout_centerVertical="true" - android:background="?attr/selectableItemBackgroundBorderless" - android:contentDescription="@string/copy_otr_clipboard_description" - android:padding="@dimen/image_button_padding" - android:src="?attr/icon_copy" - android:visibility="visible" /> + android:orientation="horizontal"> + + <ImageButton + android:id="@+id/action_copy_to_clipboard" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:background="?attr/selectableItemBackgroundBorderless" + android:contentDescription="@string/copy_otr_clipboard_description" + android:padding="@dimen/image_button_padding" + android:src="?attr/icon_copy" + android:visibility="visible" /> + </LinearLayout> </RelativeLayout> <RelativeLayout @@ -596,8 +608,10 @@ <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_alignParentStart="true" android:layout_alignParentLeft="true" android:layout_centerVertical="true" + android:layout_toStartOf="@+id/axolotl_actions" android:layout_toLeftOf="@+id/axolotl_actions" android:orientation="vertical"> @@ -618,9 +632,10 @@ android:id="@+id/axolotl_actions" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_alignParentEnd="true" android:layout_alignParentRight="true" android:layout_centerVertical="true" - android:orientation="vertical"> + android:orientation="horizontal"> <ImageButton android:id="@+id/action_copy_axolotl_to_clipboard" @@ -650,10 +665,10 @@ android:id="@+id/other_device_keys_card" android:layout_width="fill_parent" android:layout_height="wrap_content" - android:layout_marginBottom="@dimen/activity_vertical_margin" android:layout_marginLeft="@dimen/activity_horizontal_margin" - android:layout_marginRight="@dimen/activity_horizontal_margin" android:layout_marginTop="@dimen/activity_vertical_margin" + android:layout_marginRight="@dimen/activity_horizontal_margin" + android:layout_marginBottom="@dimen/activity_vertical_margin" android:visibility="gone" card_view:cardBackgroundColor="?attr/color_background_secondary"> @@ -694,11 +709,11 @@ android:id="@+id/button_bar" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignParentBottom="true" - android:layout_alignParentEnd="true" + android:layout_alignParentStart="true" android:layout_alignParentLeft="true" + android:layout_alignParentEnd="true" android:layout_alignParentRight="true" - android:layout_alignParentStart="true"> + android:layout_alignParentBottom="true"> <Button android:id="@+id/cancel_button" @@ -711,8 +726,8 @@ <View android:layout_width="1dp" android:layout_height="fill_parent" - android:layout_marginBottom="7dp" - android:layout_marginTop="7dp" /> + android:layout_marginTop="7dp" + android:layout_marginBottom="7dp" /> <Button android:id="@+id/save_button" diff --git a/src/main/res/layout/activity_fullscreen_message.xml b/src/main/res/layout/activity_media_viewer.xml index c1a1bc619..2eaad0d04 100644 --- a/src/main/res/layout/activity_fullscreen_message.xml +++ b/src/main/res/layout/activity_media_viewer.xml @@ -13,7 +13,7 @@ android:layout_alignParentRight="true" android:layout_alignParentTop="true"> - <com.github.chrisbanes.photoview.PhotoView + <com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView android:id="@id/message_image_view" android:layout_width="match_parent" android:layout_height="match_parent" @@ -32,6 +32,6 @@ android:layout_height="wrap_content" android:layout_gravity="right|top" android:layout_margin="16dp" - android:src="?attr/icon_share" /> + android:src="@drawable/ic_menu_white_24dp" /> </FrameLayout> </RelativeLayout>
\ No newline at end of file diff --git a/src/main/res/layout/activity_muc_details.xml b/src/main/res/layout/activity_muc_details.xml index e1ac744ea..29400a246 100644 --- a/src/main/res/layout/activity_muc_details.xml +++ b/src/main/res/layout/activity_muc_details.xml @@ -26,11 +26,10 @@ <android.support.v7.widget.CardView android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginBottom="@dimen/activity_vertical_margin" android:layout_marginLeft="@dimen/activity_horizontal_margin" - android:layout_marginRight="@dimen/activity_horizontal_margin" android:layout_marginTop="@dimen/activity_vertical_margin" - android:orientation="vertical" + android:layout_marginRight="@dimen/activity_horizontal_margin" + android:layout_marginBottom="@dimen/activity_vertical_margin" card_view:cardBackgroundColor="?attr/color_background_secondary"> <LinearLayout @@ -53,10 +52,10 @@ android:id="@+id/muc_display" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignParentLeft="true" android:layout_alignParentStart="true" - android:layout_toLeftOf="@+id/edit_muc_name_button" + android:layout_alignParentLeft="true" android:layout_toStartOf="@+id/edit_muc_name_button" + android:layout_toLeftOf="@+id/edit_muc_name_button" android:orientation="vertical"> <TextView @@ -76,8 +75,8 @@ android:id="@+id/jid" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignParentLeft="true" android:layout_below="@+id/muc_subject" + android:layout_alignParentLeft="true" android:text="@string/account_settings_example_jabber_id" android:textAppearance="@style/TextAppearance.Conversations.Body1" android:textIsSelectable="true" @@ -89,10 +88,10 @@ android:id="@+id/muc_editor" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_alignParentLeft="true" android:layout_alignParentStart="true" - android:layout_toLeftOf="@+id/edit_muc_name_button" + android:layout_alignParentLeft="true" android:layout_toStartOf="@+id/edit_muc_name_button" + android:layout_toLeftOf="@+id/edit_muc_name_button" android:orientation="vertical" android:visibility="gone"> @@ -130,9 +129,9 @@ android:id="@+id/edit_muc_name_button" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_alignParentTop="true" android:layout_alignParentEnd="true" android:layout_alignParentRight="true" - android:layout_alignParentTop="true" android:alpha="?attr/icon_alpha" android:background="?attr/selectableItemBackgroundBorderless" android:padding="@dimen/image_button_padding" @@ -153,10 +152,12 @@ android:layout_marginEnd="@dimen/avatar_item_distance" android:layout_marginRight="@dimen/avatar_item_distance" android:adjustViewBounds="true" - android:maxHeight="384dp" android:maxWidth="384dp" + android:maxHeight="384dp" android:padding="1dp" android:scaleType="centerCrop" + app:riv_border_color="?attr/color_border" + app:riv_border_width="1dp" app:riv_corner_radius="@dimen/rounded_image_border" /> </RelativeLayout> @@ -279,12 +280,12 @@ <android.support.v7.widget.CardView android:id="@+id/media_wrapper" - android:layout_width="fill_parent" + android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginBottom="@dimen/activity_vertical_margin" android:layout_marginLeft="@dimen/activity_horizontal_margin" - android:layout_marginRight="@dimen/activity_horizontal_margin" android:layout_marginTop="@dimen/activity_vertical_margin" + android:layout_marginRight="@dimen/activity_horizontal_margin" + android:layout_marginBottom="@dimen/activity_vertical_margin" card_view:cardBackgroundColor="?attr/color_background_secondary"> <LinearLayout @@ -295,14 +296,14 @@ <android.support.v7.widget.RecyclerView android:id="@+id/media" android:layout_width="match_parent" - android:layout_height="wrap_content" + android:layout_height="100dp" android:layout_marginEnd="-2dp" android:layout_marginStart="-2dp" android:orientation="horizontal" - android:paddingBottom="@dimen/card_padding_list" - android:paddingEnd="@dimen/card_padding_regular" android:paddingStart="@dimen/card_padding_regular" - android:paddingTop="@dimen/card_padding_regular" /> + android:paddingTop="@dimen/card_padding_regular" + android:paddingEnd="@dimen/card_padding_regular" + android:paddingBottom="@dimen/card_padding_list" /> <LinearLayout android:layout_width="wrap_content" @@ -327,11 +328,10 @@ <android.support.v7.widget.CardView android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginBottom="@dimen/activity_vertical_margin" android:layout_marginLeft="@dimen/activity_horizontal_margin" - android:layout_marginRight="@dimen/activity_horizontal_margin" android:layout_marginTop="@dimen/activity_vertical_margin" - android:orientation="vertical" + android:layout_marginRight="@dimen/activity_horizontal_margin" + android:layout_marginBottom="@dimen/activity_vertical_margin" card_view:cardBackgroundColor="?attr/color_background_secondary"> <LinearLayout @@ -355,10 +355,12 @@ android:id="@+id/your_photo" android:layout_width="72dp" android:layout_height="72dp" - android:layout_alignParentEnd="false" android:layout_alignParentLeft="true" + android:layout_alignParentEnd="false" android:layout_alignParentRight="false" android:padding="1dp" + app:riv_border_color="?attr/color_border" + app:riv_border_width="1dp" app:riv_corner_radius="@dimen/rounded_image_border" /> <LinearLayout @@ -369,7 +371,7 @@ android:orientation="vertical" android:paddingLeft="@dimen/avatar_item_distance"> - <android.support.text.emoji.widget.EmojiTextView + <TextView android:id="@+id/muc_your_nick" android:layout_width="wrap_content" android:layout_height="wrap_content" @@ -429,11 +431,10 @@ android:id="@+id/muc_more_details" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginBottom="@dimen/activity_vertical_margin" android:layout_marginLeft="@dimen/activity_horizontal_margin" - android:layout_marginRight="@dimen/activity_horizontal_margin" android:layout_marginTop="@dimen/activity_vertical_margin" - android:orientation="vertical" + android:layout_marginRight="@dimen/activity_horizontal_margin" + android:layout_marginBottom="@dimen/activity_vertical_margin" card_view:cardBackgroundColor="?attr/color_background_secondary"> <LinearLayout @@ -458,7 +459,7 @@ android:layout_weight="1" android:divider="?android:dividerHorizontal" android:orientation="vertical" - android:showDividers="middle"></LinearLayout> + android:showDividers="middle" /> </LinearLayout> </android.support.v7.widget.CardView> diff --git a/src/main/res/layout/activity_publish_profile_picture.xml b/src/main/res/layout/activity_publish_profile_picture.xml index 1db8a159d..0fb666365 100644 --- a/src/main/res/layout/activity_publish_profile_picture.xml +++ b/src/main/res/layout/activity_publish_profile_picture.xml @@ -6,80 +6,89 @@ <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent" - android:background="?attr/color_background_tertiary"> + android:background="?attr/color_background_tertiary" + android:orientation="vertical"> <include android:id="@+id/toolbar" layout="@layout/toolbar" /> - <android.support.v7.widget.CardView - android:layout_width="fill_parent" - android:layout_height="wrap_content" - android:layout_below="@id/toolbar" - android:layout_marginBottom="@dimen/activity_vertical_margin" - android:layout_marginLeft="@dimen/activity_horizontal_margin" - android:layout_marginRight="@dimen/activity_horizontal_margin" - android:layout_marginTop="@dimen/activity_vertical_margin" - card_view:cardBackgroundColor="?attr/color_background_secondary"> - - <LinearLayout + <ScrollView + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_above="@id/button_bar" + android:layout_below="@id/toolbar"> + + <android.support.v7.widget.CardView android:layout_width="match_parent" android:layout_height="wrap_content" - android:gravity="center_horizontal" - android:orientation="vertical" - android:padding="@dimen/card_padding_regular"> - - <FrameLayout - android:id="@+id/account_image_wrapper" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginBottom="8dp" - android:layout_marginTop="@dimen/publish_avatar_top_margin"> - - <com.makeramen.roundedimageview.RoundedImageView - android:id="@+id/account_image" + android:layout_marginLeft="@dimen/activity_horizontal_margin" + android:layout_marginTop="@dimen/activity_vertical_margin" + android:layout_marginRight="@dimen/activity_horizontal_margin" + android:layout_marginBottom="@dimen/activity_vertical_margin" + card_view:cardBackgroundColor="?attr/color_background_secondary"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center_horizontal" + android:orientation="vertical" + android:padding="@dimen/card_padding_regular"> + + <FrameLayout + android:id="@+id/account_image_wrapper" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:adjustViewBounds="true" - android:maxHeight="384dp" - android:maxWidth="384dp" - android:padding="1dp" - android:scaleType="centerCrop" - app:riv_corner_radius="@dimen/rounded_image_border" /> - </FrameLayout> - - <TextView - android:id="@+id/hint" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/touch_to_choose_picture" - android:textAppearance="@style/TextAppearance.Conversations.Body1" /> - - <TextView - android:id="@+id/secondary_hint" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="@string/or_long_press_for_default" - android:textAppearance="@style/TextAppearance.Conversations.Body1" /> - - <TextView - android:id="@+id/hint_or_warning" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginBottom="8dp" - android:layout_marginTop="8dp" - android:textAppearance="@style/TextAppearance.Conversations.Body1" /> - - </LinearLayout> - </android.support.v7.widget.CardView> + android:layout_marginTop="@dimen/publish_avatar_top_margin" + android:layout_marginBottom="8dp"> + + <com.makeramen.roundedimageview.RoundedImageView + android:id="@+id/account_image" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:adjustViewBounds="true" + android:maxWidth="384dp" + android:maxHeight="384dp" + android:padding="1dp" + android:scaleType="centerCrop" + app:riv_border_color="?attr/color_border" + app:riv_border_width="1dp" + app:riv_corner_radius="@dimen/rounded_image_border" /> + </FrameLayout> + + <TextView + android:id="@+id/hint" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/touch_to_choose_picture" + android:textAppearance="@style/TextAppearance.Conversations.Body1" /> + + <TextView + android:id="@+id/secondary_hint" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/or_long_press_for_default" + android:textAppearance="@style/TextAppearance.Conversations.Body1" /> + + <TextView + android:id="@+id/hint_or_warning" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="8dp" + android:layout_marginBottom="8dp" + android:textAppearance="@style/TextAppearance.Conversations.Body1" /> + + </LinearLayout> + </android.support.v7.widget.CardView> + </ScrollView> <LinearLayout android:id="@+id/button_bar" - android:layout_width="wrap_content" + android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_alignParentBottom="true" android:layout_alignParentLeft="true" - android:layout_alignParentRight="true"> + android:layout_alignParentRight="true" + android:layout_alignParentBottom="true"> <Button android:id="@+id/cancel_button" @@ -92,8 +101,8 @@ <View android:layout_width="1dp" android:layout_height="fill_parent" - android:layout_marginBottom="7dp" android:layout_marginTop="7dp" + android:layout_marginBottom="7dp" android:background="?attr/divider" /> <Button diff --git a/src/main/res/layout/contact.xml b/src/main/res/layout/contact.xml index 9635d4c47..09e0ac9bf 100644 --- a/src/main/res/layout/contact.xml +++ b/src/main/res/layout/contact.xml @@ -13,6 +13,8 @@ android:layout_width="56dp" android:layout_height="56dp" android:layout_alignParentLeft="true" + app:riv_border_color="?attr/color_border" + app:riv_border_width="1dp" app:riv_corner_radius="@dimen/rounded_image_border" /> <LinearLayout @@ -23,7 +25,7 @@ android:orientation="vertical" android:paddingLeft="@dimen/avatar_item_distance"> - <android.support.text.emoji.widget.EmojiTextView + <TextView android:id="@+id/contact_display_name" android:layout_width="wrap_content" android:layout_height="wrap_content" diff --git a/src/main/res/layout/conversation_list_row.xml b/src/main/res/layout/conversation_list_row.xml index cbd884ae2..ed0e9e1cc 100644 --- a/src/main/res/layout/conversation_list_row.xml +++ b/src/main/res/layout/conversation_list_row.xml @@ -24,6 +24,8 @@ android:layout_alignParentLeft="true" android:padding="1dp" android:scaleType="centerCrop" + app:riv_border_color="?attr/color_border" + app:riv_border_width="1dp" app:riv_corner_radius="@dimen/rounded_image_border" /> <RelativeLayout @@ -144,7 +146,7 @@ android:visibility="gone" app:backgroundColor="?attr/colorAccent" /> - <de.pixart.messenger.ui.widget.UnreadCountCustomView + <de.pixart.messenger.ui.widget.FailedCountCustomView android:id="@+id/conversation_failed" android:layout_width="?attr/IconSize" android:layout_height="?attr/IconSize" diff --git a/src/main/res/layout/fragment_conversation.xml b/src/main/res/layout/fragment_conversation.xml index eca67f91e..8917333c1 100644 --- a/src/main/res/layout/fragment_conversation.xml +++ b/src/main/res/layout/fragment_conversation.xml @@ -30,6 +30,7 @@ android:layout_height="wrap_content" android:layout_alignBottom="@+id/messages_view" android:layout_alignParentEnd="true" + android:layout_alignParentRight="true" android:alpha="0.85" android:src="?attr/icon_scroll_down" android:visibility="gone" @@ -42,8 +43,10 @@ android:layout_width="?attr/IconSize" android:layout_height="?attr/IconSize" android:layout_alignEnd="@+id/scroll_to_bottom_button" + android:layout_alignRight="@+id/scroll_to_bottom_button" android:layout_alignTop="@+id/scroll_to_bottom_button" android:layout_marginEnd="8dp" + android:layout_marginRight="8dp" android:layout_marginTop="16dp" android:elevation="8dp" android:visibility="gone" @@ -84,7 +87,9 @@ android:id="@+id/media_preview" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_alignParentLeft="true" android:layout_alignParentStart="true" + android:layout_toLeftOf="@+id/textSendButton" android:layout_toStartOf="@+id/textSendButton" android:background="@drawable/message_bubble_sent_blue" android:orientation="horizontal" diff --git a/src/main/res/layout/message_content.xml b/src/main/res/layout/message_content.xml index 296944565..044f8c64d 100644 --- a/src/main/res/layout/message_content.xml +++ b/src/main/res/layout/message_content.xml @@ -13,6 +13,8 @@ android:maxHeight="500dp" android:maxWidth="500dp" android:scaleType="centerCrop" + app:riv_border_color="?attr/color_border" + app:riv_border_width="1dp" app:riv_corner_radius="@dimen/rounded_image_border" /> <de.pixart.messenger.ui.widget.CopyTextView diff --git a/src/main/res/layout/message_received.xml b/src/main/res/layout/message_received.xml index f8b5fce95..2c6fd044a 100644 --- a/src/main/res/layout/message_received.xml +++ b/src/main/res/layout/message_received.xml @@ -16,6 +16,8 @@ android:layout_alignParentLeft="true" android:layout_alignParentTop="true" android:scaleType="fitXY" + app:riv_border_color="?attr/color_border" + app:riv_border_width="1dip" app:riv_corner_radius="@dimen/rounded_image_border" /> <LinearLayout @@ -23,7 +25,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentBottom="true" - android:layout_marginLeft="-7dp" + android:layout_marginLeft="4dp" android:layout_marginRight="4dp" android:layout_toRightOf="@+id/message_photo" android:background="@drawable/message_bubble_received_light" diff --git a/src/main/res/layout/message_sent.xml b/src/main/res/layout/message_sent.xml index 188e466d5..2526e4dd2 100644 --- a/src/main/res/layout/message_sent.xml +++ b/src/main/res/layout/message_sent.xml @@ -23,6 +23,8 @@ android:layout_width="48dp" android:layout_height="48dp" android:scaleType="fitXY" + app:riv_border_color="?attr/color_border" + app:riv_border_width="1dp" app:riv_corner_radius="@dimen/rounded_image_border" /> <View @@ -37,7 +39,7 @@ android:layout_height="wrap_content" android:layout_alignParentBottom="true" android:layout_marginLeft="4dp" - android:layout_marginRight="-7dp" + android:layout_marginRight="4dp" android:layout_toLeftOf="@+id/message_photo_box" android:layout_toStartOf="@+id/message_photo_box" android:background="@drawable/message_bubble_sent_blue" diff --git a/src/main/res/layout/message_status.xml b/src/main/res/layout/message_status.xml index bd731cbe4..d42637175 100644 --- a/src/main/res/layout/message_status.xml +++ b/src/main/res/layout/message_status.xml @@ -29,6 +29,8 @@ android:padding="0dp" android:scaleType="fitXY" android:visibility="gone" + app:riv_border_color="?attr/color_border" + app:riv_border_width="1dp" app:riv_corner_radius="@dimen/rounded_image_border" /> <TextView diff --git a/src/main/res/menu/contact_details.xml b/src/main/res/menu/contact_details.xml index 50243150b..b9268e4f9 100644 --- a/src/main/res/menu/contact_details.xml +++ b/src/main/res/menu/contact_details.xml @@ -11,7 +11,7 @@ <item android:id="@+id/action_share" - android:icon="?attr/icon_share" + android:icon="@drawable/ic_share_white_24dp" android:orderInCategory="15" app:showAsAction="always" android:title="@string/share_uri_with"> diff --git a/src/main/res/menu/editaccount.xml b/src/main/res/menu/editaccount.xml index fbc5841ea..09919b9fe 100644 --- a/src/main/res/menu/editaccount.xml +++ b/src/main/res/menu/editaccount.xml @@ -5,11 +5,12 @@ android:id="@+id/action_change_presence" android:icon="@drawable/ic_new_releases_white_24dp" android:title="@string/edit_status_message" - app:showAsAction="always" /> + app:showAsAction="ifRoom" /> - <item android:id="@+id/action_share" + <item + android:id="@+id/action_share" + android:icon="@drawable/ic_share_white_24dp" android:title="@string/share_uri_with" - android:icon="?attr/icon_share" app:showAsAction="always"> <menu> <item @@ -21,57 +22,60 @@ <item android:id="@+id/action_share_barcode" android:title="@string/share_as_barcode" /> - <item - android:id="@+id/action_show_qr_code" - android:title="@string/show_qr_code" /> </menu> </item> <item + android:id="@+id/action_show_qr_code" + android:icon="@drawable/ic_qrcode_white_24dp" + android:title="@string/show_qr_code" + app:showAsAction="always" /> + + <item android:id="@+id/action_show_block_list" - app:showAsAction="always" android:icon="@drawable/ic_speaker_notes_off_white_24dp" - android:title="@string/show_block_list" /> + android:title="@string/show_block_list" + app:showAsAction="ifRoom" /> <item android:id="@+id/action_renew_certificate" - app:showAsAction="never" android:title="@string/action_renew_certificate" - android:visible="false" /> + android:visible="false" + app:showAsAction="never" /> <item android:id="@+id/action_server_info_show_more" android:checkable="true" android:checked="false" - app:showAsAction="never" - android:title="@string/server_info_show_more" /> + android:title="@string/server_info_show_more" + app:showAsAction="never" /> <item android:id="@+id/action_mam_prefs" android:icon="@drawable/ic_cloud_white_24dp" - app:showAsAction="always" - android:title="@string/mam_prefs" /> + android:title="@string/mam_prefs" + app:showAsAction="ifRoom" /> <item android:id="@+id/action_show_password" - app:showAsAction="never" - android:title="@string/show_password" /> + android:title="@string/show_password" + app:showAsAction="never" /> <item android:id="@+id/action_change_password_on_server" - app:showAsAction="always" android:icon="@drawable/ic_vpn_key_white_24dp" - android:title="@string/change_password" /> + android:title="@string/change_password" + app:showAsAction="ifRoom" /> <item android:id="@+id/mgmt_account_reconnect" - app:showAsAction="never" - android:title="@string/mgmt_account_reconnect" /> + android:title="@string/mgmt_account_reconnect" + app:showAsAction="never" /> <item android:id="@+id/mgmt_account_announce_pgp" android:title="@string/mgmt_account_publish_pgp" /> <item android:id="@+id/action_settings" android:orderInCategory="100" - app:showAsAction="never" - android:title="@string/action_settings" /> + android:title="@string/action_settings" + app:showAsAction="never" /> </menu>
\ No newline at end of file diff --git a/src/main/res/menu/media_viewer.xml b/src/main/res/menu/media_viewer.xml new file mode 100644 index 000000000..c3898c79b --- /dev/null +++ b/src/main/res/menu/media_viewer.xml @@ -0,0 +1,17 @@ +<menu xmlns:android="http://schemas.android.com/apk/res/android"> + + <item + android:id="@+id/action_share" + android:icon="?attr/icon_share" + android:orderInCategory="10" + android:title="@string/share" /> + <item + android:id="@+id/action_open" + android:orderInCategory="20" + android:title="@string/action_open" /> + <item + android:id="@+id/action_delete" + android:icon="?attr/icon_delete" + android:orderInCategory="30" + android:title="@string/action_delete" /> +</menu>
\ No newline at end of file diff --git a/src/main/res/menu/muc_details.xml b/src/main/res/menu/muc_details.xml index b5e74f271..9f12e7333 100644 --- a/src/main/res/menu/muc_details.xml +++ b/src/main/res/menu/muc_details.xml @@ -4,7 +4,7 @@ <item android:id="@+id/action_share" - android:icon="?attr/icon_share" + android:icon="@drawable/ic_share_white_24dp" android:orderInCategory="15" android:title="@string/share_uri_with" app:showAsAction="always"> diff --git a/src/main/res/values-fil/strings.xml b/src/main/res/values-fil/strings.xml index f71f50243..fe40ec093 100644 --- a/src/main/res/values-fil/strings.xml +++ b/src/main/res/values-fil/strings.xml @@ -317,7 +317,7 @@ <string name="delete_x_file">Burahin %s</string> <string name="file">kikil</string> <string name="open_x_file">Buksan %s</string> - <string name="sending_file">pagpapadala (%1$d% nakumpleto)</string> + <string name="sending_file">pagpapadala (%1$d%% nakumpleto)</string> <string name="preparing_file">Paghahanda sa kikil para sa transmisyon</string> <string name="x_file_offered_for_download">%s inaalok para idownload</string> <string name="cancel_transmission">Kansel transmisyon</string> diff --git a/src/main/res/values-tr/strings.xml b/src/main/res/values-tr/strings.xml index d9528a409..9b5a71e96 100644 --- a/src/main/res/values-tr/strings.xml +++ b/src/main/res/values-tr/strings.xml @@ -433,7 +433,7 @@ <string name="download_failed_could_not_write_file">İndirme başarısız: Dosya yazılamadı</string> <string name="action_check_update">Güncellemeleri kontrol et</string> <string name="title_activity_updater">Servisi güncelle</string> - <string name="update_available">Sürüm% 1 $ s mevcut. \ N \ nFilesize:% 2 $ s \ n \ n% 1 $ s sürümüne şimdi mi güncelleştiniz?</string> + <string name="update_available">Sürüm %1$s mevcut.\n\nFilesize: %2$s \n\n%1$s sürümüne şimdi mi güncelleştiniz?</string> <string name="remind_later">sonra</string> <string name="update">güncelle</string> <string name="no_update_available">Güncelleme mevcut değil</string> diff --git a/src/main/res/values-w384dp/dimens.xml b/src/main/res/values-w384dp/dimens.xml index e9350addd..a04ce6763 100644 --- a/src/main/res/values-w384dp/dimens.xml +++ b/src/main/res/values-w384dp/dimens.xml @@ -2,7 +2,6 @@ <!-- 384dp is the screen width of the Nexus 4. Something like a Moto G is smaller but a Nexus 5X is larger --> <!-- https://material.io/devices/ --> <dimen name="fineprint_size">12sp</dimen> - <dimen name="swipe_handle_size">48dp</dimen> <dimen name="audio_player_width">288dp</dimen> <dimen name="avatar_on_details_screen_size">72dp</dimen> <dimen name="media_size">64dp</dimen> <!-- ideally not larger than avatar_on_details_screen --> diff --git a/src/main/res/values/attrs.xml b/src/main/res/values/attrs.xml index 672cb00fb..c537fd248 100644 --- a/src/main/res/values/attrs.xml +++ b/src/main/res/values/attrs.xml @@ -58,6 +58,7 @@ <attr name="icon_chat" format="reference" /> <attr name="icon_copy" format="reference" /> <attr name="icon_discard" format="reference" /> + <attr name="icon_show_qr_code" format="reference" /> <attr name="icon_download" format="reference" /> <attr name="icon_edit" format="reference" /> <attr name="icon_edit_body" format="reference" /> @@ -74,6 +75,7 @@ <attr name="icon_secure" format="reference" /> <attr name="icon_settings" format="reference" /> <attr name="icon_share" format="reference" /> + <attr name="icon_delete" format="reference" /> <attr name="icon_import_export" format="reference" /> <attr name="icon_scan_qr_code" format="reference" /> <attr name="icon_enable_undecided_device" format="reference" /> diff --git a/src/main/res/values/colors.xml b/src/main/res/values/colors.xml index 6ea10548f..8dfec1519 100644 --- a/src/main/res/values/colors.xml +++ b/src/main/res/values/colors.xml @@ -27,7 +27,7 @@ <color name="grey900">#ff282828</color> <color name="red800">#ffc62828</color> <color name="red500">#fff44336</color> - <color name="red_a700">#ffd50000</color> + <color name="red700">#ffd50000</color> <color name="orange500">#ffff9800</color> <color name="bubble">#ff2e4272</color> <color name="realwhite">#ffffffff</color> diff --git a/src/main/res/values/defaults.xml b/src/main/res/values/defaults.xml index e213ea773..29e560124 100644 --- a/src/main/res/values/defaults.xml +++ b/src/main/res/values/defaults.xml @@ -41,7 +41,7 @@ \n\nhttp://hc.apache.org/httpcomponents-client\n(Apache License, Version 2.0) \n\nhttp://hc.apache.org/httpcomponents-core\n(Apache License, Version 2.0) \n\nhttps://github.com/bumptech/glide\n(BSD, The MIT License (MIT) and Apache License, Version 2.0) - \n\nhttps://github.com/chrisbanes/PhotoView\n(Apache License, Version 2.0) + \n\nhttps://github.com/davemorrissey/subsampling-scale-image-view\n(Apache License, Version 2.0) \n\nhttps://github.com/rtoshiro/FullscreenVideoView\n(Apache License, Version 2.0) \n\nhttps://github.com/mangstadt/ez-vcard\n(FreeBSD) \n\nhttps://github.com/googlesamples/easypermissions\n(Apache License, Version 2.0) @@ -51,7 +51,6 @@ \n\nhttp://leafletjs.com/\n(BSD 2-Clause) \n\nhttps://www.openstreetmap.org/\n(Open Database License) \n\nhttp://xmpp.rocks/\n(The MIT License (MIT)) - \n\nhttps://github.com/championswimmer/SimpleFingerGestures_Android_Library/\n(Apache License, Version 2.0) </string> <string name="default_resource" translatable="false">Phone</string> diff --git a/src/main/res/values/dimens.xml b/src/main/res/values/dimens.xml index 526eb7d40..0c6ef41fb 100644 --- a/src/main/res/values/dimens.xml +++ b/src/main/res/values/dimens.xml @@ -27,5 +27,5 @@ <dimen name="scan_dot_size">8dp</dimen> <dimen name="background_image_opacity">0.12</dimen> - <dimen name="rounded_image_border">8dp</dimen> + <dimen name="rounded_image_border">5dp</dimen> </resources> diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index 62b415cf3..34c227331 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -75,7 +75,7 @@ <string name="sharing_files_please_wait">Sharing files. Please wait…</string> <string name="action_clear_history">Clear history</string> <string name="clear_conversation_history">Clear Conversation History</string> - <string name="clear_histor_msg">Do you want to delete all messages within this Conversation?\n\n<b>Warning:</b> This will not influence messages stored on other devices or servers.</string> + <string name="clear_histor_msg">Are you sure you want to delete all messages within this conversation?\n\n<b>Warning:</b> This will not delete copies of those messages that are stored on other devices or servers.</string> <string name="delete_messages">Delete messages</string> <string name="also_end_conversation">End this conversation afterwards</string> <string name="choose_presence">Choose device</string> @@ -295,8 +295,8 @@ <string name="send_again">Send again</string> <string name="file_url">File URL</string> <string name="url_copied_to_clipboard">URL copied to clipboard</string> - <string name="scan_qr_code">Scan barcode</string> - <string name="show_qr_code">Show barcode</string> + <string name="scan_qr_code">Scan QR code</string> + <string name="show_qr_code">Show QR code</string> <string name="show_block_list">Show block list</string> <string name="account_details">Account details</string> <string name="verify_otr">Verify OTR</string> @@ -611,19 +611,19 @@ <string name="this_device_has_been_verified">This device has been verified</string> <string name="copy_fingerprint">Copy fingerprint</string> <string name="all_omemo_keys_have_been_verified">All OMEMO keys have been verified</string> - <string name="barcode_does_not_contain_fingerprints_for_this_conversation">Barcode does not contain fingerprints for this conversation.</string> + <string name="barcode_does_not_contain_fingerprints_for_this_conversation">QR code does not contain fingerprints for this conversation.</string> <string name="verified_fingerprints">Verified fingerprints</string> - <string name="use_camera_icon_to_scan_barcode">Use the camera to scan a contacts barcode</string> + <string name="use_camera_icon_to_scan_barcode">Use the camera to scan a contacts QR code</string> <string name="please_wait_for_keys_to_be_fetched">Please wait for keys to be fetched</string> - <string name="share_as_barcode">Share as Barcode</string> + <string name="share_as_barcode">Share as QR code</string> <string name="share_as_uri">Share as XMPP URI</string> <string name="share_as_http">Share as HTTP link</string> <string name="blindly_trusted_omemo_keys">Blindly trusted OMEMO keys</string> <string name="pref_blind_trust_before_verification">Blind Trust Before Verification</string> <string name="pref_blind_trust_before_verification_summary">Automatically trust all new devices from contacts that haven’t been verified before.</string> <string name="not_trusted">Untrusted</string> - <string name="invalid_barcode">Invalid barcode</string> - <string name="verify_with_qr_code">verify with barcode</string> + <string name="invalid_barcode">Invalid QR code</string> + <string name="verify_with_qr_code">verify with QR code</string> <string name="i_followed_this_link_from_a_trusted_source">I followed this link from a trusted source</string> <string name="verifying_omemo_keys_trusted_source">You are about to verify the OMEMO keys of %1$s after clicking a link. This is only secure if you followed this link from a trusted source where only %2$s could have published this link.</string> <string name="verify_omemo_keys">Verify OMEMO fingerprints</string> @@ -826,4 +826,14 @@ <string name="action_group_details">Group details</string> <string name="view_media">View media</string> <string name="media_browser">Media browser</string> + <string name="account_status_stream_opening_error">Stream opening error</string> + <string name="action_open">Open</string> + <string name="action_delete">Delete</string> + <string name="security_violation_not_attaching_file">File omitted due to security violation.</string> + <string name="delete_file_dialog">Delete file</string> + <string name="delete_file_dialog_msg">Are you sure you want to delete this file?\n\n<b>Warning:</b> This will not delete copies of this file that are stored on other devices or servers. </string> + <string name="cancelled">cancelled</string> + <string name="remote_server_timeout">Remote server timeout</string> + <string name="already_drafting_message">You are already drafting a message.</string> + </resources> diff --git a/src/main/res/values/themes.xml b/src/main/res/values/themes.xml index bbfaf6060..342ed8aa1 100644 --- a/src/main/res/values/themes.xml +++ b/src/main/res/values/themes.xml @@ -13,7 +13,7 @@ <item name="color_background_primary">@color/grey50</item> <item name="color_background_secondary">@color/grey200</item> <item name="color_background_tertiary">@color/grey300</item> - <item name="color_warning">@color/red_a700</item> + <item name="color_warning">@color/red700</item> <item name="TextColorOnline">@color/green500</item> <item name="TextColorError">@color/red800</item> @@ -80,6 +80,7 @@ <item name="icon_cancel" type="reference">@drawable/ic_cancel_black_24dp</item> <item name="icon_chat" type="reference">@drawable/ic_action_chat</item> <item name="icon_copy" type="reference">@drawable/ic_content_copy_grey600_24dp</item> + <item name="icon_show_qr_code" type="reference">@drawable/ic_qrcode_grey600_24dp</item> <item name="icon_discard" type="reference">@drawable/ic_delete_white_24dp</item> <item name="icon_download" type="reference">@drawable/ic_file_download_white_24dp</item> <item name="icon_edit" type="reference">@drawable/ic_edit_white_24dp</item> @@ -98,8 +99,9 @@ <item name="icon_settings" type="reference">@drawable/ic_settings_black_24dp</item> <item name="icon_import_export" type="reference">@drawable/ic_import_export_white_24dp </item> - <item name="icon_share" type="reference">@drawable/ic_share_white_24dp</item> - <item name="icon_scan_qr_code" type="reference">@drawable/ic_barcode_scan_white_24dp</item> + <item name="icon_delete" type="reference">@drawable/ic_delete_black_24dp</item> + <item name="icon_share" type="reference">@drawable/ic_share_black_24dp</item> + <item name="icon_scan_qr_code" type="reference">@drawable/ic_qrcode_scan_white_24dp</item> <item name="icon_scroll_down" type="reference">@drawable/ic_scroll_to_end_black</item> <!-- settings--> @@ -165,7 +167,7 @@ <item name="color_background_primary">@color/grey700</item> <item name="color_background_secondary">@color/grey800</item> <item name="color_background_tertiary">@color/grey900</item> - <item name="color_warning">@color/red_a700</item> + <item name="color_warning">@color/red700</item> <item name="TextColorOnline">@color/green500</item> <item name="TextColorError">@color/red500</item> @@ -251,6 +253,7 @@ <item name="icon_add_person" type="reference">@drawable/ic_person_add_white_24dp</item> <item name="icon_cancel" type="reference">@drawable/ic_cancel_white_24dp</item> <item name="icon_copy" type="reference">@drawable/ic_content_copy_white_24dp</item> + <item name="icon_show_qr_code" type="reference">@drawable/ic_qrcode_white_24dp</item> <item name="icon_discard" type="reference">@drawable/ic_delete_white_24dp</item> <item name="icon_download" type="reference">@drawable/ic_file_download_white_24dp</item> <item name="icon_edit" type="reference">@drawable/ic_edit_white_24dp</item> @@ -269,8 +272,9 @@ <item name="icon_settings" type="reference">@drawable/ic_settings_white_24dp</item> <item name="icon_import_export" type="reference">@drawable/ic_import_export_white_24dp </item> + <item name="icon_delete" type="reference">@drawable/ic_delete_white_24dp</item> <item name="icon_share" type="reference">@drawable/ic_share_white_24dp</item> - <item name="icon_scan_qr_code" type="reference">@drawable/ic_barcode_scan_white_24dp</item> + <item name="icon_scan_qr_code" type="reference">@drawable/ic_qrcode_scan_white_24dp</item> <item name="icon_scroll_down" type="reference">@drawable/ic_scroll_to_end_white</item> <item name="icon_notifications" type="reference">@drawable/ic_notifications_white_24dp diff --git a/src/standardPush/java/de/pixart/messenger/services/InstanceIdService.java b/src/standardPush/java/de/pixart/messenger/services/InstanceIdService.java index 3db7219c0..3e9a00a3c 100644 --- a/src/standardPush/java/de/pixart/messenger/services/InstanceIdService.java +++ b/src/standardPush/java/de/pixart/messenger/services/InstanceIdService.java @@ -1,6 +1,7 @@ package de.pixart.messenger.services; import android.content.Intent; + import com.google.firebase.iid.FirebaseInstanceIdService; public class InstanceIdService extends FirebaseInstanceIdService { diff --git a/src/standardPush/java/de/pixart/messenger/services/MaintenanceReceiver.java b/src/standardPush/java/de/pixart/messenger/services/MaintenanceReceiver.java index 9ee13c6cd..9368913bc 100644 --- a/src/standardPush/java/de/pixart/messenger/services/MaintenanceReceiver.java +++ b/src/standardPush/java/de/pixart/messenger/services/MaintenanceReceiver.java @@ -32,5 +32,6 @@ public class MaintenanceReceiver extends BroadcastReceiver { Log.d(Config.LOGTAG, "unable to renew instance token", e); } }).start(); + } }
\ No newline at end of file diff --git a/src/standardPush/java/de/pixart/messenger/services/PushManagementService.java b/src/standardPush/java/de/pixart/messenger/services/PushManagementService.java index b9aff036b..7daa552d4 100644 --- a/src/standardPush/java/de/pixart/messenger/services/PushManagementService.java +++ b/src/standardPush/java/de/pixart/messenger/services/PushManagementService.java @@ -2,7 +2,12 @@ package de.pixart.messenger.services; import android.util.Log; +import com.google.android.gms.common.ConnectionResult; +import com.google.android.gms.common.GoogleApiAvailability; +import com.google.firebase.iid.FirebaseInstanceId; + import de.pixart.messenger.Config; +import de.pixart.messenger.R; import de.pixart.messenger.entities.Account; import de.pixart.messenger.utils.Namespace; import de.pixart.messenger.utils.PhoneHelper; @@ -14,8 +19,6 @@ import rocks.xmpp.addr.Jid; public class PushManagementService { - private static final Jid APP_SERVER = Jid.of("p2.siacs.eu"); - protected final XmppConnectionService mXmppConnectionService; PushManagementService(XmppConnectionService service) { @@ -26,7 +29,8 @@ public class PushManagementService { Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": has push support"); retrieveFcmInstanceToken(token -> { final String androidId = PhoneHelper.getAndroidId(mXmppConnectionService); - IqPacket packet = mXmppConnectionService.getIqGenerator().pushTokenToAppServer(APP_SERVER, token, androidId); + final Jid appServer = Jid.of(mXmppConnectionService.getString(R.string.app_server)); + IqPacket packet = mXmppConnectionService.getIqGenerator().pushTokenToAppServer(appServer, token, androidId); mXmppConnectionService.sendIqPacket(account, packet, (a, p) -> { Element command = p.findChild("command", "http://jabber.org/protocol/commands"); if (p.getType() == IqPacket.TYPE.RESULT && command != null) { diff --git a/src/standardPush/java/de/pixart/messenger/services/PushMessageReceiver.java b/src/standardPush/java/de/pixart/messenger/services/PushMessageReceiver.java index d45ded454..816d11118 100644 --- a/src/standardPush/java/de/pixart/messenger/services/PushMessageReceiver.java +++ b/src/standardPush/java/de/pixart/messenger/services/PushMessageReceiver.java @@ -3,6 +3,9 @@ package de.pixart.messenger.services; import android.content.Intent; import android.util.Log; +import com.google.firebase.messaging.FirebaseMessagingService; +import com.google.firebase.messaging.RemoteMessage; + import java.util.Map; import de.pixart.messenger.Config; |