...
 
Commits (130)
github: inputmice
liberapay: inputmice
custom: https://paypal.me/ConversationsIM
custom: https://gultsch.de/donate.html
......@@ -10,7 +10,8 @@ android:
licenses:
- '.+'
before_script:
- wget -O libs/libwebrtc-m81.aar http://gultsch.de/files/libwebrtc-m81.aar
- mkdir libs
- wget -O libs/libwebrtc-m83.aar http://gultsch.de/files/libwebrtc-m83.aar
script:
- ./gradlew assembleConversationsFreeSystemRelease
- ./gradlew assembleQuicksyFreeCompatRelease
......
# Changelog
### Version 2.8.8
* Fixed notifications not showing up under certain conditions
* Fixed compatibility issues and crashes related to A/V calls
### Version 2.8.7
* Show help button if A/V call fails
* Fixed some annoying crashes
* Fixed Jingle connections (file transfer + calls) with bare JIDs
### Version 2.8.6
* Offer to record voice message when callee is busy
### Version 2.8.5
* Reduce echo during calls on some devices
* Fix login when passwords contains special characters
* Play dial and busy tones on speaker during video calls
### Version 2.8.4
* Rework Login with certificate UI
* Add ability to pin chats on top (add to favorites)
### Version 2.8.3
* Move call icon to the left in order to keep other toolbar icons in a consistent place
* Show call duration during audio calls
* Tie breaking for A/V calls (the same two people calling each other at the same time)
### Version 2.8.2
* Add button to switch camea during video call
* Add button to switch camera during video call
* Fixed voice calls on tablets
### Version 2.8.1
......
......@@ -32,7 +32,7 @@
* End-to-end encryption with [OMEMO](http://conversations.im/omemo/) or [OpenPGP](http://openpgp.org/about/)
* Send and receive images as well as other kind of files
* Make audio and video calls
* Encrypted audio and video calls (DTLS-SRTP)
* Share your location
* Send voice messages
* Indication when your contact has read your message
......@@ -91,23 +91,9 @@ Google [Play Store](https://play.google.com/store/apps/details?id=eu.siacs.conve
Buying the App from the Play Store will also give you access to our [beta test](#beta).
#### I don't have a Google Account but I would still like to make a contribution
#### I don't have a Google Account but I would still like to make a donation
I accept donations over PayPal, bank transfer and various crypto currencies. For donations via PayPal use the button below:
[![Donate with PayPal](https://www.paypalobjects.com/en_US/i/btn/btn_donate_LG.gif)](https://www.paypal.me/ConversationsIM)
**Disclaimer:** I'm not a huge fan of PayPal and their business policies. For
larger contributions please get in touch with me beforehand and we can talk
about bank transfer (SEPA).
##### Crypto currencies
Bitcoin: `3KAD8vew6tPZDjiUJNnZ3YUoUxrCEVNwFL`
Bitcoin Cash: `16ABkXzYAwWz8Y5DcWFfbBRqL63g3hzEaU`
Ether: `0x5c4e5239cd9c6f4a909e4e8361526e2e3c8ba9fa`
I’m listing several options to support me financially on [my website](https://gultsch.de/donate.html). Among other things [Liberapay](https://liberapay.com/iNPUTmice/donate), [GitHub Sponsors](https://github.com/sponsors/inputmice) and bank transfer.
#### How do I create an account?
XMPP, like email, is a federated protocol, which means that there is not one company you can create an *official XMPP account* with. Instead there are hundreds, or even thousands, of providers out there. One of those providers is our very own [conversations.im](https://account.conversations.im). If you don’t like to use *conversations.im* use a web search engine of your choice to find another provider. Or maybe your university has one. Or you can run your own. Or ask a friend to run one. Once you've found one, you can use Conversations to create an account. Just select *register new account* on server within the create account dialog.
......@@ -387,7 +373,7 @@ There are XMPP Clients available for all major platforms.
#### Windows / Linux
For your desktop computer we recommend that you use [Gajim](https://gajim.org). You need to install the plugins `OMEMO`, `HTTP Upload` and `URL image preview` to get the best compatibility with Conversations. Plugins can be installed from within the app.
#### iOS
Unfortunately we don‘t have a recommendation for iPhones right now. There are two clients available [ChatSecure](https://chatsecure.org/) and [Monal](https://monal.im/). Both with their own pros and cons.
Unfortunately we don‘t have a recommendation for iPhones right now. There are three clients available [Siskin](https://siskin.im/), [ChatSecure](https://chatsecure.org/) and [Monal](https://monal.im/). Each with their own pros and cons.
### Development
......@@ -416,25 +402,6 @@ There are two build flavors available. *free* and *playstore*. Unless you know w
[![Build Status](https://travis-ci.org/inputmice/Conversations.svg?branch=development)](https://travis-ci.org/inputmice/Conversations)
#### How do I update/add external libraries?
If the library you want to update is in Maven Central or JCenter (or has its own
Maven repo), add it or update its version in `build.gradle`. If the library is
in the `libs/` directory, you can update it using a subtree merge by doing the
following (using `minidns` as an example):
git remote add minidns https://github.com/rtreffer/minidns.git
git fetch minidns
git merge -s subtree minidns master
To add a new dependency to the `libs/` directory (replacing "name", "branch" and
"url" as necessary):
git remote add name url
git merge -s ours --no-commit name/branch
git read-tree --prefix=libs/name -u name/branch
git commit -m "Subtree merged in name"
#### How do I debug Conversations
If something goes wrong Conversations usually exposes very little information in
......
......@@ -119,7 +119,7 @@ images.each do |source_filename, settings|
else
path = "../src/#{output_parts[0]}/res/drawable-#{resolution}/#{output_parts[1]}.png"
end
execute_cmd "#{inkscape} -f #{source_filename} -z -C -w #{width} -h #{height} -e #{path}"
execute_cmd "#{inkscape} #{source_filename} -C -w #{width} -h #{height} -o #{path}"
top = []
right = []
......
......@@ -67,7 +67,7 @@ dependencies {
implementation 'com.makeramen:roundedimageview:2.3.0'
implementation "com.wefika:flowlayout:0.4.1"
implementation 'net.ypresto.androidtranscoder:android-transcoder:0.3.0'
implementation project(':libs:xmpp-addr')
implementation 'org.jxmpp:jxmpp-jid:0.6.4'
implementation 'org.osmdroid:osmdroid-android:6.1.5'
implementation 'org.hsluv:hsluv:0.2'
implementation 'org.conscrypt:conscrypt-android:2.2.1'
......@@ -77,10 +77,10 @@ dependencies {
implementation "com.squareup.retrofit2:retrofit:2.6.4"
implementation "com.squareup.retrofit2:converter-gson:2.6.4"
//okhttp needs to stick with 3.12.x
implementation 'com.squareup.okhttp3:okhttp:3.12.10'
implementation 'com.squareup.okhttp3:okhttp:3.12.12'
implementation 'com.google.guava:guava:27.1-android'
quicksyImplementation 'io.michaelrocks:libphonenumber-android:8.11.1'
implementation fileTree(include: ['libwebrtc-m81.aar'], dir: 'libs')
implementation fileTree(include: ['libwebrtc-m83.aar'], dir: 'libs')
}
ext {
......@@ -95,8 +95,8 @@ android {
defaultConfig {
minSdkVersion 16
targetSdkVersion 28
versionCode 382
versionName "2.8.2"
versionCode 394
versionName "2.8.8"
archivesBaseName += "-$versionName"
applicationId "eu.siacs.conversations"
resValue "string", "applicationId", applicationId
......
* Move call icon to the left in order to keep other toolbar icons in a consistent place
* Show call duration during audio calls
* Tie breaking for A/V calls (the same two people calling each other at the same time)
* Rework Login with certificate UI
* Add ability to pin chats on top (add to favorites)
* Reduce echo during calls on some devices
* Fix login when passwords contains special characters
* Play dial and busy tones on speaker during video calls
* Offer to record voice message when callee is busy
* Show help button if A/V call fails
* Fixed some annoying crashes
* Fixed Jingle connections (file transfer + calls) with bare JIDs
* Fixed notifications not showing up under certain conditions
* Fixed compatibility issues and crashes related to A/V calls
......@@ -11,7 +11,7 @@ Features:
* End-to-end encryption with either <a href="http://conversations.im/omemo/">OMEMO</a> or <a href="http://openpgp.org/about/">OpenPGP</a>
* Sending and receiving images
* Make audio and video calls
* Encrypted audio and video calls (DTLS-SRTP)
* Intuitive UI that follows Android Design guidelines
* Pictures / Avatars for your Contacts
* Syncs with desktop client
......
apply plugin: 'java-library'
repositories {
google()
jcenter()
mavenCentral()
}
dependencies {
implementation 'rocks.xmpp:precis:1.0.0'
}
sourceCompatibility = "8"
targetCompatibility = "8"
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2017 Christian Schudt
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package rocks.xmpp.addr;
import java.text.Collator;
import java.util.Arrays;
/**
* Abstract Jid implementation for both full and bare JIDs.
*
* @author Christian Schudt
*/
abstract class AbstractJid implements Jid {
/**
* Checks if the JID is a full JID.
* <blockquote>
* <p>The term "full JID" refers to an XMPP address of the form &lt;localpart@domainpart/resourcepart&gt; (for a particular authorized client or device associated with an account) or of the form &lt;domainpart/resourcepart&gt; (for a particular resource or script associated with a server).</p>
* </blockquote>
*
* @return True, if the JID is a full JID; otherwise false.
*/
@Override
public final boolean isFullJid() {
return getResource() != null;
}
/**
* Checks if the JID is a bare JID.
* <blockquote>
* <p>The term "bare JID" refers to an XMPP address of the form &lt;localpart@domainpart&gt; (for an account at a server) or of the form &lt;domainpart&gt; (for a server).</p>
* </blockquote>
*
* @return True, if the JID is a bare JID; otherwise false.
*/
@Override
public final boolean isBareJid() {
return getResource() == null;
}
@Override
public final boolean isDomainJid() {
return getLocal() == null;
}
@Override
public final boolean equals(Object o) {
if (o == this) {
return true;
}
if (!(o instanceof Jid)) {
return false;
}
Jid other = (Jid) o;
return (getLocal() == other.getLocal() || getLocal() != null && getLocal().equals(other.getLocal()))
&& (getDomain() == other.getDomain() || getDomain() != null && getDomain().equals(other.getDomain()))
&& (getResource() == other.getResource() || getResource() != null && getResource().equals(other.getResource()));
}
@Override
public final int hashCode() {
return Arrays.hashCode(new String[]{getLocal(), getDomain(), getResource()});
}
/**
* Compares this JID with another JID. First domain parts are compared. If these are equal, local parts are compared
* and if these are equal, too, resource parts are compared.
*
* @param o The other JID.
* @return The comparison result.
*/
@Override
public final int compareTo(Jid o) {
if (this == o) {
return 0;
}
if (o != null) {
final Collator collator = Collator.getInstance();
int result;
// First compare domain parts.
if (getDomain() != null) {
result = o.getDomain() != null ? collator.compare(getDomain(), o.getDomain()) : -1;
} else {
result = o.getDomain() != null ? 1 : 0;
}
// If the domains are equal, compare local parts.
if (result == 0) {
if (getLocal() != null) {
// If this local part is not null, but the other is null, move this down (1).
result = o.getLocal() != null ? collator.compare(getLocal(), o.getLocal()) : 1;
} else {
// If this local part is null, but the other is not, move this up (-1).
result = o.getLocal() != null ? -1 : 0;
}
}
// If the local parts are equal, compare resource parts.
if (result == 0) {
if (getResource() != null) {
// If this resource part is not null, but the other is null, move this down (1).
return o.getResource() != null ? collator.compare(getResource(), o.getResource()) : 1;
} else {
// If this resource part is null, but the other is not, move this up (-1).
return o.getResource() != null ? -1 : 0;
}
}
return result;
} else {
return -1;
}
}
@Override
public final int length() {
return toString().length();
}
@Override
public final char charAt(int index) {
return toString().charAt(index);
}
@Override
public final CharSequence subSequence(int start, int end) {
return toString().subSequence(start, end);
}
/**
* Returns the JID in its string representation, i.e. [ localpart "@" ] domainpart [ "/" resourcepart ].
*
* @return The JID.
* @see #toEscapedString()
*/
@Override
public final String toString() {
return toString(getLocal(), getDomain(), getResource());
}
@Override
public final String toEscapedString() {
return toString(getEscapedLocal(), getDomain(), getResource());
}
static String toString(String local, String domain, String resource) {
StringBuilder sb = new StringBuilder();
if (local != null) {
sb.append(local).append('@');
}
sb.append(domain);
if (resource != null) {
sb.append('/').append(resource);
}
return sb.toString();
}
}
This diff is collapsed.
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2017 Christian Schudt
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package rocks.xmpp.addr;
/**
* Represents a malformed JID in order to handle the <code>jid-malformed</code> error.
* <p>
* This class is not intended to be publicly instantiable, but is used for malformed JIDs during parsing automatically.
*
* @author Christian Schudt
* @see <a href="https://xmpp.org/rfcs/rfc6120.html#stanzas-error-conditions-jid-malformed">RFC 6120, 8.3.3.8. jid-malformed</a>
*/
public final class MalformedJid extends AbstractJid {
private static final long serialVersionUID = -2896737611021417985L;
private final String localPart;
private final String domainPart;
private final String resourcePart;
private final Throwable cause;
static MalformedJid of(final String jid, final Throwable cause) {
// Do some basic parsing without any further checks or validation.
final StringBuilder sb = new StringBuilder(jid);
// 1. Remove any portion from the first '/' character to the end of the
// string (if there is a '/' character present).
final int indexOfResourceDelimiter = jid.indexOf('/');
final String resourcePart;
if (indexOfResourceDelimiter > -1) {
resourcePart = sb.substring(indexOfResourceDelimiter + 1);
sb.delete(indexOfResourceDelimiter, sb.length());
} else {
resourcePart = null;
}
// 2. Remove any portion from the beginning of the string to the first
// '@' character (if there is an '@' character present).
final int indexOfAt = jid.indexOf('@');
final String localPart;
if (indexOfAt > -1) {
localPart = sb.substring(0, indexOfAt);
sb.delete(0, indexOfAt + 1);
} else {
localPart = null;
}
return new MalformedJid(localPart, sb.toString(), resourcePart, cause);
}
private MalformedJid(final String localPart, final String domainPart, final String resourcePart, final Throwable cause) {
this.localPart = localPart;
this.domainPart = domainPart;
this.resourcePart = resourcePart;
this.cause = cause;
}
@Override
public final Jid asBareJid() {
return new MalformedJid(localPart, domainPart, null, cause);
}
@Override
public Jid withLocal(CharSequence local) {
return new MalformedJid(local.toString(), domainPart, resourcePart, cause);
}
@Override
public Jid withResource(CharSequence resource) {
return new MalformedJid(localPart, domainPart, resource.toString(), cause);
}
@Override
public Jid atSubdomain(CharSequence subdomain) {
if (subdomain == null) {
throw new NullPointerException();
}
return new MalformedJid(localPart, subdomain + "." + domainPart, resourcePart, cause);
}
@Override
public final String getLocal() {
return localPart;
}
@Override
public final String getEscapedLocal() {
return localPart;
}
@Override
public final String getDomain() {
return domainPart;
}
@Override
public final String getResource() {
return resourcePart;
}
/**
* Gets the cause why the JID is malformed.
*
* @return The cause.
*/
public final Throwable getCause() {
return cause;
}
}
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2016 Christian Schudt
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* Provides classes for the <a href="https://tools.ietf.org/html/rfc7622">XMPP Address Format</a> (JID).
*
* @see <a href="https://tools.ietf.org/html/rfc7622">Extensible Messaging and Presence Protocol (XMPP): Address Format</a>
*/
package rocks.xmpp.addr;
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2016 Christian Schudt
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package rocks.xmpp.util.cache;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* A simple directory based cache for caching of persistent items like avatars or entity capabilities.
*
* @author Christian Schudt
*/
public final class DirectoryCache implements Map<String, byte[]> {
private final Path cacheDirectory;
public DirectoryCache(Path cacheDirectory) {
this.cacheDirectory = cacheDirectory;
}
@Override
public final int size() {
try (final Stream<Path> files = cacheContent()) {
return (int) Math.min(files.count(), Integer.MAX_VALUE);
}
}
@Override
public final boolean isEmpty() {
try (final Stream<Path> files = cacheContent()) {
return files.findAny().map(file -> Boolean.FALSE).orElse(Boolean.TRUE);
}
}
@Override
public final boolean containsKey(Object key) {
return Files.exists(cacheDirectory.resolve(key.toString()));
}
@Override
public final boolean containsValue(Object value) {
throw new UnsupportedOperationException();
}
@Override
public final byte[] get(final Object key) {
return Optional.ofNullable(key).map(Object::toString).filter(((Predicate<String>) String::isEmpty).negate()).map(cacheDirectory::resolve).filter(Files::isReadable).map(file -> {
try {
return Files.readAllBytes(file);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}).orElse(null);
}
@Override
public final byte[] put(String key, byte[] value) {
// Make sure the directory exists.
byte[] data = get(key);
if (!Arrays.equals(data, value))
try {
if (Files.notExists(cacheDirectory)) {
Files.createDirectories(cacheDirectory);
}
Path file = cacheDirectory.resolve(key);
Files.write(file, value);
} catch (IOException e) {
throw new UncheckedIOException(e);
}
return data;
}
@Override
public final byte[] remove(Object key) {
byte[] data = get(key);
try {
Files.deleteIfExists(cacheDirectory.resolve(key.toString()));
} catch (IOException e) {
throw new UncheckedIOException(e);
}
return data;
}
@Override
public final void putAll(Map<? extends String, ? extends byte[]> m) {
m.forEach(this::put);
}
@Override
public final void clear() {
try {
Files.walkFileTree(cacheDirectory, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.deleteIfExists(file);
return FileVisitResult.CONTINUE;
}
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
// Don't delete the cache directory itself.
if (!Files.isSameFile(dir, cacheDirectory)) {
Files.deleteIfExists(dir);
}
return FileVisitResult.CONTINUE;
}
});
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
@Override
public final Set<String> keySet() {
try (final Stream<Path> files = Files.list(cacheDirectory)) {
return Collections.unmodifiableSet(files.map(Path::getFileName).map(Path::toString).collect(Collectors.toSet()));
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
@Override
public final Collection<byte[]> values() {
throw new UnsupportedOperationException();
}
@Override
public final Set<Entry<String, byte[]>> entrySet() {
throw new UnsupportedOperationException();
}
@Override
public final void forEach(final BiConsumer<? super String, ? super byte[]> action) {
if (Files.exists(cacheDirectory))
try (final Stream<Path> files = cacheContent().filter(Files::isReadable)) {
files.forEach(file -> {
try {
action.accept(file.getFileName().toString(), Files.readAllBytes(file));
} catch (final IOException e) {
throw new UncheckedIOException(e);
}
});
}
}
@SuppressWarnings("StreamResourceLeak")
private final Stream<Path> cacheContent() {
try {
return Files.walk(cacheDirectory).filter(Files::isRegularFile);
} catch (final IOException e) {
throw new UncheckedIOException(e);
}
}
}
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2016 Christian Schudt
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package rocks.xmpp.util.cache;
import java.util.Collection;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.function.BiFunction;
import java.util.function.Function;
/**
* A simple concurrent implementation of a least-recently-used cache.
* <p>
* This cache is keeps a maximal number of items in memory and removes the least-recently-used item, when new items are added.
*
* @param <K> The key.
* @param <V> The value.
* @author Christian Schudt
* @see <a href="http://javadecodedquestions.blogspot.de/2013/02/java-cache-static-data-loading.html">http://javadecodedquestions.blogspot.de/2013/02/java-cache-static-data-loading.html</a>
* @see <a href="http://stackoverflow.com/a/22891780">http://stackoverflow.com/a/22891780</a>
*/
public final class LruCache<K, V> implements Map<K, V> {
private final int maxEntries;
private final Map<K, V> map;
final Queue<K> queue;
public LruCache(final int maxEntries) {
this.maxEntries = maxEntries;
this.map = new ConcurrentHashMap<>(maxEntries);
// Don't use a ConcurrentLinkedQueue here.
// There's a JDK bug, leading to OutOfMemoryError and high CPU usage:
// https://bugs.openjdk.java.net/browse/JDK-8054446
this.queue = new ConcurrentLinkedDeque<>();
}
@Override
public final int size() {
return map.size();
}
@Override
public final boolean isEmpty() {
return map.isEmpty();
}
@Override
public final boolean containsKey(final Object key) {
return map.containsKey(key);
}
@Override
public final boolean containsValue(final Object value) {
return map.containsValue(value);
}
@SuppressWarnings("unchecked")
@Override
public final V get(final Object key) {
final V v = map.get(key);
if (v != null) {
// Remove the key from the queue and re-add it to the tail. It is now the most recently used key.
keyUsed((K) key);
}
return v;
}
@Override
public final V put(final K key, final V value) {
V v = map.put(key, value);
keyUsed(key);
limit();
return v;
}
@Override
public final V remove(final Object key) {
queue.remove(key);
return map.remove(key);
}
@Override
public final void putAll(final Map<? extends K, ? extends V> m) {
for (Map.Entry<? extends K, ? extends V> entry : m.entrySet()) {
put(entry.getKey(), entry.getValue());
}
}
@Override
public final void clear() {
queue.clear();
map.clear();
}
@Override
public final Set<K> keySet() {
return map.keySet();
}
@Override
public final Collection<V> values() {
return map.values();
}
@Override
public final Set<Entry<K, V>> entrySet() {
return map.entrySet();
}
// Default methods
@Override
public final V putIfAbsent(final K key, final V value) {
final V v = map.putIfAbsent(key, value);
if (v == null) {
keyUsed(key);
}
limit();
return v;
}
@Override
public final boolean remove(final Object key, final Object value) {
final boolean removed = map.remove(key, value);
if (removed) {
queue.remove(key);
}
return removed;
}
@Override
public final boolean replace(final K key, final V oldValue, final V newValue) {
final boolean replaced = map.replace(key, oldValue, newValue);
if (replaced) {
keyUsed(key);
}
return replaced;
}
@Override
public final V replace(final K key, final V value) {
final V v = map.replace(key, value);
if (v != null) {
keyUsed(key);
}
return v;
}
@Override
public final V computeIfAbsent(final K key, final Function<? super K, ? extends V> mappingFunction) {
return map.computeIfAbsent(key, mappingFunction.<V>andThen(v -> {
keyUsed(key);
limit();
return v;
}));
}
@Override
public final V computeIfPresent(final K key, final BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
return map.computeIfPresent(key, remappingFunction.<V>andThen(v -> {
keyUsed(key);
limit();
return v;
}));
}
@Override
public final V compute(final K key, final BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
return map.compute(key, remappingFunction.<V>andThen(v -> {
keyUsed(key);
limit();
return v;
}));
}
@Override
public final V merge(K key, V value, BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
return map.merge(key, value, remappingFunction.<V>andThen(v -> {
keyUsed(key);
limit();
return v;
}));
}
private void limit() {
while (queue.size() > maxEntries) {
final K oldestKey = queue.poll();
if (oldestKey != null) {
map.remove(oldestKey);
}
}
}
private void keyUsed(final K key) {
// remove it from the queue and re-add it, to make it the most recently used key.
queue.remove(key);
queue.offer(key);
}
}
\ No newline at end of file
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2016 Christian Schudt
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
/**
* Provides simple cache implementations.
*/
package rocks.xmpp.util.cache;
\ No newline at end of file
screenshots.png

166 KB | W: | H:

screenshots.png

171 KB | W: | H:

screenshots.png
screenshots.png
screenshots.png
screenshots.png
  • 2-up
  • Swipe
  • Onion skin
No preview for this file type
include ':libs:xmpp-addr'
rootProject.name = 'Conversations'
......@@ -38,6 +38,40 @@
<data android:mimeType="application/vnd.conversations.backup" />
<data android:scheme="file" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="content" />
<data android:host="*" />
<data android:mimeType="*/*" />
<data android:pathPattern=".*\\.ceb" />
<data android:pathPattern=".*\\..*\\.ceb" />
<data android:pathPattern=".*\\..*\\..*\\.ceb" />
<data android:pathPattern=".*\\..*\\..*\\..*\\.ceb" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.ceb" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.ceb" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\.ceb" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="file" />
<data android:host="*" />
<data android:mimeType="*/*" />
<data android:pathPattern=".*\\.ceb" />
<data android:pathPattern=".*\\..*\\.ceb" />
<data android:pathPattern=".*\\..*\\..*\\.ceb" />
<data android:pathPattern=".*\\..*\\..*\\..*\\.ceb" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\.ceb" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\.ceb" />
<data android:pathPattern=".*\\..*\\..*\\..*\\..*\\..*\\..*\\.ceb" />
</intent-filter>
</activity>
</application>
</manifest>
package eu.siacs.conversations.entities;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonSyntaxException;
import com.google.gson.annotations.SerializedName;
import eu.siacs.conversations.xmpp.Jid;
public class AccountConfiguration {
private static final Gson GSON = new GsonBuilder().create();
public Protocol protocol;
public String address;
public String password;
public Jid getJid() {
return Jid.ofEscaped(address);
}
public static AccountConfiguration parse(final String input) {
final AccountConfiguration c;
try {
c = GSON.fromJson(input, AccountConfiguration.class);
} catch (JsonSyntaxException e) {
throw new IllegalArgumentException("Not a valid JSON string", e);
}
Preconditions.checkArgument(
c.protocol == Protocol.XMPP,
"Protocol must be XMPP"
);
Preconditions.checkArgument(
c.address != null && c.getJid().isBareJid() && !c.getJid().isDomainJid(),
"Invalid XMPP address"
);
Preconditions.checkArgument(
c.password != null && c.password.length() > 0,
"No password specified"
);
return c;
}
public enum Protocol {
@SerializedName("xmpp") XMPP,
}
}
......@@ -46,7 +46,7 @@ import eu.siacs.conversations.ui.ManageAccountActivity;
import eu.siacs.conversations.utils.BackupFileHeader;
import eu.siacs.conversations.utils.Compatibility;
import eu.siacs.conversations.utils.SerialSingleThreadExecutor;
import