Merge tag '1.3.0'

This commit is contained in:
Daniel Gultsch 2015-05-02 12:11:14 +02:00
commit f97aaab014
198 changed files with 2997 additions and 462 deletions

View file

@ -1,5 +1,13 @@
###Changelog
####Version 1.3.0
* swipe conversations to end them
* quickly enable / disable account via slider
* share multiple images at once
* expert option to distrust system CAs
* mlink compatibility
* bug fixes
####Version 1.2.0
* Send current location. (requires [plugin](https://play.google.com/store/apps/details?id=eu.siacs.conversations.sharelocation))
* Invite multiple contacts at once

View file

@ -2,7 +2,7 @@
Conversations: the very last word in instant messaging
[![Google Play](http://developer.android.com/images/brand/en_generic_rgb_wo_45.png)](https://play.google.com/store/apps/details?id=eu.siacs.conversations)
[![Google Play](http://developer.android.com/images/brand/en_generic_rgb_wo_60.png)](https://play.google.com/store/apps/details?id=eu.siacs.conversations) [![Amazon App Store](https://images-na.ssl-images-amazon.com/images/G/01/AmazonMobileApps/amazon-apps-store-us-black.png)](http://www.amazon.com/dp/B00WD35AAC/)
![screenshots](https://raw.githubusercontent.com/siacs/Conversations/master/screenshots.png)
@ -17,7 +17,8 @@ Conversations: the very last word in instant messaging
## Features
* End-to-end encryption with either [OTR](https://otr.cypherpunks.ca/) or [OpenPGP](http://www.openpgp.org/about_openpgp/)
* Sending and receiving images
* Send and receive images as well as other kind of files
* Share your location via an external [plug-in](https://play.google.com/store/apps/details?id=eu.siacs.conversations.sharelocation)
* Indication when your contact has read your message
* Intuitive UI that follows Android Design guidelines
* Pictures / Avatars for your Contacts

View file

@ -34,6 +34,7 @@ dependencies {
compile 'com.google.zxing:core:3.1.0'
compile 'com.google.zxing:android-integration:3.1.0'
compile 'de.measite.minidns:minidns:0.1.3'
compile 'de.timroes.android:EnhancedListView:0.3.4'
}
android {
@ -43,8 +44,8 @@ android {
defaultConfig {
minSdkVersion 14
targetSdkVersion 21
versionCode 56
versionName "1.2.0"
versionCode 60
versionName "1.3.0"
}
compileOptions {

View file

@ -122,6 +122,13 @@
<data android:mimeType="*/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
</activity>
<activity
android:name="de.duenndns.ssl.MemorizingActivity"

View file

@ -28,6 +28,7 @@ public final class Config {
public static final boolean NO_PROXY_LOOKUP = false; //useful to debug ibb
public static final boolean DISABLE_STRING_PREP = false; // setting to true might increase startup performance
public static final boolean EXTENDED_SM_LOGGING = false; // log stanza counts
public static final long MILLISECONDS_IN_DAY = 24 * 60 * 60 * 1000;
public static final long MAM_MAX_CATCHUP = MILLISECONDS_IN_DAY / 2;

View file

@ -182,7 +182,7 @@ public class OtrEngine extends OtrCryptoEngineImpl implements OtrEngineHost {
packet.setBody(body);
packet.addChild("private", "urn:xmpp:carbons:2");
packet.addChild("no-copy", "urn:xmpp:hints");
packet.addChild("no-store", "urn:xmpp:hints");
packet.addChild("no-permanent-store", "urn:xmpp:hints");
try {
Jid jid = Jid.fromSessionID(session);
@ -202,20 +202,7 @@ public class OtrEngine extends OtrCryptoEngineImpl implements OtrEngineHost {
@Override
public void messageFromAnotherInstanceReceived(SessionID session) {
try {
Jid jid = Jid.fromSessionID(session);
Conversation conversation = mXmppConnectionService.find(account, jid);
String id = conversation == null ? null : conversation.getLastReceivedOtrMessageId();
if (id != null) {
MessagePacket packet = mXmppConnectionService.getMessageGenerator().generateOtrError(jid,id);
packet.setFrom(account.getJid());
mXmppConnectionService.sendMessagePacket(account,packet);
Log.d(Config.LOGTAG,packet.toString());
Log.d(Config.LOGTAG,account.getJid().toBareJid().toString()+": unreadable OTR message in "+conversation.getName());
}
} catch (InvalidJidException e) {
return;
}
sendOtrErrorMessage(session, "Message from another OTR-instance received");
}
@Override
@ -267,9 +254,28 @@ public class OtrEngine extends OtrCryptoEngineImpl implements OtrEngineHost {
}
@Override
public void unreadableMessageReceived(SessionID arg0) throws OtrException {
public void unreadableMessageReceived(SessionID session) throws OtrException {
Log.d(Config.LOGTAG,"unreadable message received");
throw new OtrException(new Exception("unreadable message received"));
sendOtrErrorMessage(session, "You sent me an unreadable OTR-encrypted message");
}
public void sendOtrErrorMessage(SessionID session, String errorText) {
try {
Jid jid = Jid.fromSessionID(session);
Conversation conversation = mXmppConnectionService.find(account, jid);
String id = conversation == null ? null : conversation.getLastReceivedOtrMessageId();
if (id != null) {
MessagePacket packet = mXmppConnectionService.getMessageGenerator()
.generateOtrError(jid, id, errorText);
packet.setFrom(account.getJid());
mXmppConnectionService.sendMessagePacket(account,packet);
Log.d(Config.LOGTAG,packet.toString());
Log.d(Config.LOGTAG,account.getJid().toBareJid().toString()
+": unreadable OTR message in "+conversation.getName());
}
} catch (InvalidJidException e) {
return;
}
}
@Override

View file

@ -229,11 +229,17 @@ public class Account extends AbstractEntity {
return jid.getResourcepart();
}
public void setResource(final String resource) {
try {
jid = Jid.fromParts(jid.getLocalpart(), jid.getDomainpart(), resource);
} catch (final InvalidJidException ignored) {
public boolean setResource(final String resource) {
final String oldResource = jid.getResourcepart();
if (oldResource == null || !oldResource.equals(resource)) {
try {
jid = Jid.fromParts(jid.getLocalpart(), jid.getDomainpart(), resource);
return true;
} catch (final InvalidJidException ignored) {
return true;
}
}
return false;
}
public Jid getJid() {

View file

@ -430,23 +430,31 @@ public class Message extends AbstractEntity {
}
public boolean bodyContainsDownloadable() {
/**
* there are a few cases where spaces result in an unwanted behavior, e.g.
* "http://example.com/image.jpg text that will not be shown /abc.png"
* or more than one image link in one message.
*/
if (body.contains(" ")) {
return false;
}
try {
URL url = new URL(this.getBody());
URL url = new URL(body);
if (!url.getProtocol().equalsIgnoreCase("http")
&& !url.getProtocol().equalsIgnoreCase("https")) {
return false;
}
if (url.getPath() == null) {
String sUrlPath = url.getPath();
if (sUrlPath == null || sUrlPath.isEmpty()) {
return false;
}
String[] pathParts = url.getPath().split("/");
String filename;
if (pathParts.length > 0) {
filename = pathParts[pathParts.length - 1].toLowerCase();
} else {
return false;
}
String[] extensionParts = filename.split("\\.");
int iSlashIndex = sUrlPath.lastIndexOf('/') + 1;
String sLastUrlPath = sUrlPath.substring(iSlashIndex).toLowerCase();
String[] extensionParts = sLastUrlPath.split("\\.");
if (extensionParts.length == 2
&& Arrays.asList(Downloadable.VALID_IMAGE_EXTENSIONS).contains(
extensionParts[extensionParts.length - 1])) {

View file

@ -71,6 +71,7 @@ public class MessageGenerator extends AbstractGenerator {
MessagePacket packet = preparePacket(message, addDelay);
packet.addChild("private", "urn:xmpp:carbons:2");
packet.addChild("no-copy", "urn:xmpp:hints");
packet.addChild("no-permanent-store", "urn:xmpp:hints");
try {
packet.setBody(otrSession.transformSending(message.getBody())[0]);
return packet;
@ -172,7 +173,7 @@ public class MessageGenerator extends AbstractGenerator {
return receivedPacket;
}
public MessagePacket generateOtrError(Jid to, String id) {
public MessagePacket generateOtrError(Jid to, String id, String errorText) {
MessagePacket packet = new MessagePacket();
packet.setType(MessagePacket.TYPE_ERROR);
packet.setAttribute("id",id);
@ -181,7 +182,7 @@ public class MessageGenerator extends AbstractGenerator {
error.setAttribute("code","406");
error.setAttribute("type","modify");
error.addChild("not-acceptable","urn:ietf:params:xml:ns:xmpp-stanzas");
error.addChild("text").setContent("unreadable OTR message received");
error.addChild("text").setContent("?OTR Error:" + errorText);
return packet;
}
}

View file

@ -54,4 +54,11 @@ public class PresenceGenerator extends AbstractGenerator {
}
return packet;
}
public PresencePacket sendOfflinePresence(Account account) {
PresencePacket packet = new PresencePacket();
packet.setFrom(account.getJid());
packet.setAttribute("type","unavailable");
return packet;
}
}

View file

@ -391,15 +391,17 @@ public class MessageParser extends AbstractParser implements
private void parseNonMessage(Element packet, Account account) {
final Jid from = packet.getAttributeAsJid("from");
if (account.getJid().equals(from)) {
return;
}
if (extractChatState(from == null ? null : mXmppConnectionService.find(account,from), packet)) {
mXmppConnectionService.updateConversationUi();
}
Element invite = extractInvite(packet);
if (invite != null) {
Conversation conversation = mXmppConnectionService.findOrCreateConversation(account, from, true);
Invite invite = extractInvite(packet);
if (invite != null && invite.jid != null) {
Conversation conversation = mXmppConnectionService.findOrCreateConversation(account, invite.jid, true);
if (!conversation.getMucOptions().online()) {
Element password = invite.findChild("password");
conversation.getMucOptions().setPassword(password == null ? null : password.getContent());
conversation.getMucOptions().setPassword(invite.password);
mXmppConnectionService.databaseBackend.updateConversation(conversation);
mXmppConnectionService.joinMuc(conversation);
mXmppConnectionService.updateConversationUi();
@ -439,16 +441,30 @@ public class MessageParser extends AbstractParser implements
}
}
private Element extractInvite(Element message) {
private class Invite {
Jid jid;
String password;
Invite(Jid jid, String password) {
this.jid = jid;
this.password = password;
}
}
private Invite extractInvite(Element message) {
Element x = message.findChild("x","http://jabber.org/protocol/muc#user");
if (x == null) {
x = message.findChild("x","jabber:x:conference");
}
if (x != null && x.hasChild("invite")) {
return x;
if (x != null) {
Element invite = x.findChild("invite");
if (invite != null) {
Element pw = x.findChild("password");
return new Invite(message.getAttributeAsJid("from"), pw != null ? pw.getContent(): null);
}
} else {
return null;
x = message.findChild("x","jabber:x:conference");
if (x != null) {
return new Invite(x.getAttributeAsJid("jid"),x.getAttribute("password"));
}
}
return null;
}
private void parseEvent(final Element event, final Jid from, final Account account) {

View file

@ -1,6 +1,7 @@
package eu.siacs.conversations.persistance;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
@ -42,8 +43,7 @@ public class FileBackend {
private static int IMAGE_SIZE = 1920;
private SimpleDateFormat imageDateFormat = new SimpleDateFormat(
"yyyyMMdd_HHmmssSSS", Locale.US);
private final SimpleDateFormat imageDateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US);
private XmppConnectionService mXmppConnectionService;
@ -110,9 +110,7 @@ public class FileBackend {
scalledW = size;
scalledH = (int) (h / ((double) w / size));
}
Bitmap scalledBitmap = Bitmap.createScaledBitmap(originalBitmap,
scalledW, scalledH, true);
return scalledBitmap;
return Bitmap.createScaledBitmap(originalBitmap, scalledW, scalledH, true);
} else {
return originalBitmap;
}
@ -148,31 +146,35 @@ public class FileBackend {
}
public DownloadableFile copyFileToPrivateStorage(Message message, Uri uri) throws FileCopyException {
Log.d(Config.LOGTAG, "copy " + uri.toString() + " to private storage");
String mime = mXmppConnectionService.getContentResolver().getType(uri);
String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mime);
message.setRelativeFilePath(message.getUuid() + "." + extension);
DownloadableFile file = mXmppConnectionService.getFileBackend().getFile(message);
file.getParentFile().mkdirs();
OutputStream os = null;
InputStream is = null;
try {
Log.d(Config.LOGTAG, "copy " + uri.toString() + " to private storage");
String mime = mXmppConnectionService.getContentResolver().getType(uri);
String extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mime);
message.setRelativeFilePath(message.getUuid() + "." + extension);
DownloadableFile file = mXmppConnectionService.getFileBackend().getFile(message);
file.getParentFile().mkdirs();
file.createNewFile();
OutputStream os = new FileOutputStream(file);
InputStream is = mXmppConnectionService.getContentResolver().openInputStream(uri);
os = new FileOutputStream(file);
is = mXmppConnectionService.getContentResolver().openInputStream(uri);
byte[] buffer = new byte[1024];
int length;
while ((length = is.read(buffer)) > 0) {
int length;
while ((length = is.read(buffer)) > 0) {
os.write(buffer, 0, length);
}
}
os.flush();
os.close();
is.close();
Log.d(Config.LOGTAG, "output file name " + mXmppConnectionService.getFileBackend().getFile(message));
return file;
} catch (FileNotFoundException e) {
} catch(FileNotFoundException e) {
throw new FileCopyException(R.string.error_file_not_found);
} catch (IOException e) {
e.printStackTrace();
throw new FileCopyException(R.string.error_io_exception);
} finally {
close(os);
close(is);
}
Log.d(Config.LOGTAG, "output file name " + mXmppConnectionService.getFileBackend().getFile(message));
return file;
}
public DownloadableFile copyImageToPrivateStorage(Message message, Uri image)
@ -182,49 +184,48 @@ public class FileBackend {
private DownloadableFile copyImageToPrivateStorage(Message message,
Uri image, int sampleSize) throws FileCopyException {
DownloadableFile file = getFile(message);
file.getParentFile().mkdirs();
InputStream is = null;
OutputStream os = null;
try {
InputStream is = mXmppConnectionService.getContentResolver()
.openInputStream(image);
DownloadableFile file = getFile(message);
file.getParentFile().mkdirs();
file.createNewFile();
is = mXmppConnectionService.getContentResolver().openInputStream(image);
os = new FileOutputStream(file);
Bitmap originalBitmap;
BitmapFactory.Options options = new BitmapFactory.Options();
int inSampleSize = (int) Math.pow(2, sampleSize);
Log.d(Config.LOGTAG, "reading bitmap with sample size "
+ inSampleSize);
Log.d(Config.LOGTAG, "reading bitmap with sample size " + inSampleSize);
options.inSampleSize = inSampleSize;
originalBitmap = BitmapFactory.decodeStream(is, null, options);
is.close();
if (originalBitmap == null) {
throw new FileCopyException(R.string.error_not_an_image_file);
}
Bitmap scalledBitmap = resize(originalBitmap, IMAGE_SIZE);
originalBitmap = null;
Bitmap scaledBitmap = resize(originalBitmap, IMAGE_SIZE);
int rotation = getRotation(image);
if (rotation > 0) {
scalledBitmap = rotate(scalledBitmap, rotation);
scaledBitmap = rotate(scaledBitmap, rotation);
}
OutputStream os = new FileOutputStream(file);
boolean success = scalledBitmap.compress(
Bitmap.CompressFormat.WEBP, 75, os);
boolean success = scaledBitmap.compress(Bitmap.CompressFormat.WEBP, 75, os);
if (!success) {
throw new FileCopyException(R.string.error_compressing_image);
}
os.flush();
os.close();
long size = file.getSize();
int width = scalledBitmap.getWidth();
int height = scalledBitmap.getHeight();
int width = scaledBitmap.getWidth();
int height = scaledBitmap.getHeight();
message.setBody(Long.toString(size) + ',' + width + ',' + height);
return file;
} catch (FileNotFoundException e) {
throw new FileCopyException(R.string.error_file_not_found);
} catch (IOException e) {
e.printStackTrace();
throw new FileCopyException(R.string.error_io_exception);
} catch (SecurityException e) {
throw new FileCopyException(
R.string.error_security_exception_during_image_copy);
throw new FileCopyException(R.string.error_security_exception_during_image_copy);
} catch (OutOfMemoryError e) {
++sampleSize;
if (sampleSize <= 3) {
@ -232,23 +233,24 @@ public class FileBackend {
} else {
throw new FileCopyException(R.string.error_out_of_memory);
}
} finally {
close(os);
close(is);
}
}
private int getRotation(Uri image) {
InputStream is = null;
try {
InputStream is = mXmppConnectionService.getContentResolver()
.openInputStream(image);
is = mXmppConnectionService.getContentResolver().openInputStream(image);
return ExifHelper.getOrientation(is);
} catch (FileNotFoundException e) {
return 0;
} finally {
close(is);
}
}
public Bitmap getImageFromMessage(Message message) {
return BitmapFactory.decodeFile(getFile(message).getAbsolutePath());
}
public Bitmap getThumbnail(Message message, int size, boolean cacheOnly)
throws FileNotFoundException {
Bitmap thumbnail = mXmppConnectionService.getBitmapCache().get(
@ -257,8 +259,7 @@ public class FileBackend {
File file = getFile(message);
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = calcSampleSize(file, size);
Bitmap fullsize = BitmapFactory.decodeFile(file.getAbsolutePath(),
options);
Bitmap fullsize = BitmapFactory.decodeFile(file.getAbsolutePath(),options);
if (fullsize == null) {
throw new FileNotFoundException();
}
@ -271,13 +272,11 @@ public class FileBackend {
public Uri getTakePhotoUri() {
StringBuilder pathBuilder = new StringBuilder();
pathBuilder.append(Environment
.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM));
pathBuilder.append(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM));
pathBuilder.append('/');
pathBuilder.append("Camera");
pathBuilder.append('/');
pathBuilder.append("IMG_" + this.imageDateFormat.format(new Date())
+ ".jpg");
pathBuilder.append("IMG_" + this.imageDateFormat.format(new Date()) + ".jpg");
Uri uri = Uri.parse("file://" + pathBuilder.toString());
File file = new File(uri.toString());
file.getParentFile().mkdirs();
@ -325,13 +324,13 @@ public class FileBackend {
String filename = getAvatarPath(avatar.getFilename());
file = new File(filename + ".tmp");
file.getParentFile().mkdirs();
OutputStream os = null;
try {
file.createNewFile();
FileOutputStream mFileOutputStream = new FileOutputStream(file);
os = new FileOutputStream(file);
MessageDigest digest = MessageDigest.getInstance("SHA-1");
digest.reset();
DigestOutputStream mDigestOutputStream = new DigestOutputStream(
mFileOutputStream, digest);
DigestOutputStream mDigestOutputStream = new DigestOutputStream(os, digest);
mDigestOutputStream.write(avatar.getImageAsBytes());
mDigestOutputStream.flush();
mDigestOutputStream.close();
@ -349,6 +348,8 @@ public class FileBackend {
return false;
} catch (NoSuchAlgorithmException e) {
return false;
} finally {
close(os);
}
}
avatar.size = file.length();
@ -356,8 +357,7 @@ public class FileBackend {
}
public String getAvatarPath(String avatar) {
return mXmppConnectionService.getFilesDir().getAbsolutePath()
+ "/avatars/" + avatar;
return mXmppConnectionService.getFilesDir().getAbsolutePath()+ "/avatars/" + avatar;
}
public Uri getAvatarUri(String avatar) {
@ -368,10 +368,11 @@ public class FileBackend {
if (image == null) {
return null;
}
InputStream is = null;
try {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = calcSampleSize(image, size);
InputStream is = mXmppConnectionService.getContentResolver().openInputStream(image);
is = mXmppConnectionService.getContentResolver().openInputStream(image);
Bitmap input = BitmapFactory.decodeStream(is, null, options);
if (input == null) {
return null;
@ -384,6 +385,8 @@ public class FileBackend {
}
} catch (FileNotFoundException e) {
return null;
} finally {
close(is);
}
}
@ -391,12 +394,15 @@ public class FileBackend {
if (image == null) {
return null;
}
InputStream is = null;
try {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inSampleSize = calcSampleSize(image,Math.max(newHeight, newWidth));
InputStream is = mXmppConnectionService.getContentResolver().openInputStream(image);
is = mXmppConnectionService.getContentResolver().openInputStream(image);
Bitmap source = BitmapFactory.decodeStream(is, null, options);
if (source == null) {
return null;
}
int sourceWidth = source.getWidth();
int sourceHeight = source.getHeight();
float xScale = (float) newWidth / sourceWidth;
@ -408,14 +414,15 @@ public class FileBackend {
float top = (newHeight - scaledHeight) / 2;
RectF targetRect = new RectF(left, top, left + scaledWidth, top + scaledHeight);
Bitmap dest = Bitmap.createBitmap(newWidth, newHeight, source.getConfig());
Bitmap dest = Bitmap.createBitmap(newWidth, newHeight, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(dest);
canvas.drawBitmap(source, null, targetRect, null);
return dest;
} catch (FileNotFoundException e) {
return null;
} finally {
close(is);
}
}
public Bitmap cropCenterSquare(Bitmap input, int size) {
@ -430,7 +437,7 @@ public class FileBackend {
float top = (size - outHeight) / 2;
RectF target = new RectF(left, top, left + outWidth, top + outHeight);
Bitmap output = Bitmap.createBitmap(size, size, input.getConfig());
Bitmap output = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(output);
canvas.drawBitmap(input, null, target, null);
return output;
@ -522,4 +529,13 @@ public class FileBackend {
public boolean isFileAvailable(Message message) {
return getFile(message).exists();
}
public static void close(Closeable stream) {
if (stream != null) {
try {
stream.close();
} catch (IOException e) {
}
}
}
}

View file

@ -454,7 +454,7 @@ public class NotificationService {
// nick (matched in case-insensitive manner), followed by optional
// punctuation (for example "bob: i disagree" or "how are you alice?"),
// followed by another word boundary.
return Pattern.compile("\\b" + nick + "\\p{Punct}?\\b",
return Pattern.compile("\\b" + Pattern.quote(nick) + "\\p{Punct}?\\b",
Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
}
@ -493,7 +493,7 @@ public class NotificationService {
final int cancelIcon;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mBuilder.setCategory(Notification.CATEGORY_SERVICE);
mBuilder.setSmallIcon(R.drawable.ic_import_export_white_48dp);
mBuilder.setSmallIcon(R.drawable.ic_import_export_white_24dp);
cancelIcon = R.drawable.ic_cancel_white_24dp;
} else {
mBuilder.setSmallIcon(R.drawable.ic_stat_communication_import_export);
@ -540,7 +540,7 @@ public class NotificationService {
mBuilder.setOngoing(true);
//mBuilder.setLights(0xffffffff, 2000, 4000);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mBuilder.setSmallIcon(R.drawable.ic_warning_white_36dp);
mBuilder.setSmallIcon(R.drawable.ic_warning_white_24dp);
} else {
mBuilder.setSmallIcon(R.drawable.ic_stat_alert_warning);
}

View file

@ -36,6 +36,7 @@ import org.openintents.openpgp.util.OpenPgpServiceConnection;
import java.math.BigInteger;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
@ -174,13 +175,22 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
public void onContactStatusChanged(Contact contact, boolean online) {
Conversation conversation = find(getConversations(), contact);
if (conversation != null) {
if (online && contact.getPresences().size() > 1) {
if (online) {
conversation.endOtrIfNeeded();
if (contact.getPresences().size() == 1) {
sendUnsentMessages(conversation);
}
} else {
conversation.resetOtrSession();
}
if (online && (contact.getPresences().size() == 1)) {
sendUnsentMessages(conversation);
if (contact.getPresences().size() >= 1) {
if (conversation.hasValidOtrSession()) {
String otrResource = conversation.getOtrSession().getSessionID().getUserID();
if (!(Arrays.asList(contact.getPresences().asStringArray()).contains(otrResource))) {
conversation.endOtrIfNeeded();
}
}
} else {
conversation.endOtrIfNeeded();
}
}
}
}
@ -532,9 +542,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
ExceptionHelper.init(getApplicationContext());
PRNGFixes.apply();
this.mRandom = new SecureRandom();
this.mMemorizingTrustManager = new MemorizingTrustManager(
getApplicationContext());
updateMemorizingTrustmanager();
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
final int cacheSize = maxMemory / 8;
this.mBitmapCache = new LruCache<String, Bitmap>(cacheSize) {
@ -1129,6 +1137,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
public void archiveConversation(Conversation conversation) {
getNotificationService().clear(conversation);
conversation.setStatus(Conversation.STATUS_ARCHIVED);
conversation.setNextEncryption(-1);
synchronized (this.conversations) {
@ -1538,6 +1547,9 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
for (Jid invite : jids) {
invite(conversation, invite);
}
if (account.countPresences() > 1) {
directInvite(conversation, account.getJid().toBareJid());
}
if (callback != null) {
callback.success(conversation);
}
@ -1700,6 +1712,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
}
}
}
sendOfflinePresence(account);
}
account.getXmppConnection().disconnect(force);
}
@ -2022,6 +2035,11 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
sendMessagePacket(conversation.getAccount(), packet);
}
public void directInvite(Conversation conversation, Jid jid) {
MessagePacket packet = mMessageGenerator.directInvite(conversation,jid);
sendMessagePacket(conversation.getAccount(),packet);
}
public void resetSendingToWaiting(Account account) {
for (Conversation conversation : getConversations()) {
if (conversation.getAccount() == account) {
@ -2185,6 +2203,21 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
return this.mMemorizingTrustManager;
}
public void setMemorizingTrustManager(MemorizingTrustManager trustManager) {
this.mMemorizingTrustManager = trustManager;
}
public void updateMemorizingTrustmanager() {
final MemorizingTrustManager tm;
final boolean dontTrustSystemCAs = getPreferences().getBoolean("dont_trust_system_cas", false);
if (dontTrustSystemCAs) {
tm = new MemorizingTrustManager(getApplicationContext(), null);
} else {
tm = new MemorizingTrustManager(getApplicationContext());
}
setMemorizingTrustManager(tm);
}
public PowerManager getPowerManager() {
return this.pm;
}
@ -2260,6 +2293,10 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
sendPresencePacket(account, mPresenceGenerator.sendPresence(account));
}
public void sendOfflinePresence(final Account account) {
sendPresencePacket(account, mPresenceGenerator.sendOfflinePresence(account));
}
public MessageGenerator getMessageGenerator() {
return this.mMessageGenerator;
}

View file

@ -237,6 +237,9 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
MenuItem menuItemDeleteBookmark = menu.findItem(R.id.action_delete_bookmark);
MenuItem menuItemAdvancedMode = menu.findItem(R.id.action_advanced_mode);
menuItemAdvancedMode.setChecked(mAdvancedMode);
if (mConversation == null) {
return true;
}
Account account = mConversation.getAccount();
if (account.hasBookmarkFor(mConversation.getJid().toBareJid())) {
menuItemSaveBookmark.setVisible(false);

View file

@ -256,16 +256,19 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd
MenuItem unblock = menu.findItem(R.id.action_unblock);
MenuItem edit = menu.findItem(R.id.action_edit_contact);
MenuItem delete = menu.findItem(R.id.action_delete_contact);
if (contact == null) {
return true;
}
final XmppConnection connection = contact.getAccount().getXmppConnection();
if (connection != null && connection.getFeatures().blocking()) {
if (this.contact.isBlocked()) {
menu.findItem(R.id.action_block).setVisible(false);
block.setVisible(false);
} else {
menu.findItem(R.id.action_unblock).setVisible(false);
unblock.setVisible(false);
}
} else {
menu.findItem(R.id.action_unblock).setVisible(false);
menu.findItem(R.id.action_block).setVisible(false);
unblock.setVisible(false);
block.setVisible(false);
}
if (!contact.showInRoster()) {
edit.setVisible(false);
@ -275,6 +278,7 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd
}
private void populateView() {
invalidateOptionsMenu();
setTitle(contact.getDisplayName());
if (contact.showInRoster()) {
send.setVisibility(View.VISIBLE);

View file

@ -5,6 +5,7 @@ import android.app.ActionBar;
import android.app.AlertDialog;
import android.app.FragmentTransaction;
import android.app.PendingIntent;
import android.content.ClipData;
import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
@ -22,14 +23,15 @@ import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.ListView;
import android.widget.PopupMenu;
import android.widget.PopupMenu.OnMenuItemClickListener;
import android.widget.Toast;
import net.java.otr4j.session.SessionStatus;
import de.timroes.android.listview.EnhancedListView;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import eu.siacs.conversations.R;
@ -69,15 +71,16 @@ public class ConversationActivity extends XmppActivity
private String mOpenConverstaion = null;
private boolean mPanelOpen = true;
private Uri mPendingImageUri = null;
private Uri mPendingFileUri = null;
final private List<Uri> mPendingImageUris = new ArrayList<>();
final private List<Uri> mPendingFileUris = new ArrayList<>();
private Uri mPendingGeoUri = null;
private View mContentView;
private List<Conversation> conversationList = new ArrayList<>();
private Conversation swipedConversation = null;
private Conversation mSelectedConversation = null;
private ListView listView;
private EnhancedListView listView;
private ConversationFragment mConversationFragment;
private ArrayAdapter<Conversation> listAdapter;
@ -140,13 +143,14 @@ public class ConversationActivity extends XmppActivity
@Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (savedInstanceState != null) {mOpenConverstaion = savedInstanceState.getString(
STATE_OPEN_CONVERSATION, null);
mPanelOpen = savedInstanceState.getBoolean(STATE_PANEL_OPEN, true);
String pending = savedInstanceState.getString(STATE_PENDING_URI, null);
if (pending != null) {
mPendingImageUri = Uri.parse(pending);
}
if (savedInstanceState != null) {
mOpenConverstaion = savedInstanceState.getString(STATE_OPEN_CONVERSATION, null);
mPanelOpen = savedInstanceState.getBoolean(STATE_PANEL_OPEN, true);
String pending = savedInstanceState.getString(STATE_PENDING_URI, null);
if (pending != null) {
mPendingImageUris.clear();
mPendingImageUris.add(Uri.parse(pending));
}
}
setContentView(R.layout.fragment_conversations_overview);
@ -156,7 +160,7 @@ public class ConversationActivity extends XmppActivity
transaction.replace(R.id.selected_conversation, this.mConversationFragment, "conversation");
transaction.commit();
listView = (ListView) findViewById(R.id.list);
listView = (EnhancedListView) findViewById(R.id.list);
this.listAdapter = new ConversationAdapter(this, conversationList);
listView.setAdapter(this.listAdapter);
@ -178,6 +182,73 @@ public class ConversationActivity extends XmppActivity
openConversation();
}
});
listView.setDismissCallback(new EnhancedListView.OnDismissCallback() {
@Override
public EnhancedListView.Undoable onDismiss(final EnhancedListView enhancedListView, final int position) {
final int index = listView.getFirstVisiblePosition();
View v = listView.getChildAt(0);
final int top = (v == null) ? 0 : (v.getTop() - listView.getPaddingTop());
swipedConversation = listAdapter.getItem(position);
listAdapter.remove(swipedConversation);
swipedConversation.markRead();
xmppConnectionService.getNotificationService().clear(swipedConversation);
final boolean formerlySelected = (getSelectedConversation() == swipedConversation);
if (position == 0 && listAdapter.getCount() == 0) {
endConversation(swipedConversation, false, true);
return null;
} else if (formerlySelected) {
setSelectedConversation(listAdapter.getItem(0));
ConversationActivity.this.mConversationFragment
.reInit(getSelectedConversation());
}
return new EnhancedListView.Undoable() {
@Override
public void undo() {
listAdapter.insert(swipedConversation, position);
if (formerlySelected) {
setSelectedConversation(swipedConversation);
ConversationActivity.this.mConversationFragment
.reInit(getSelectedConversation());
}
swipedConversation = null;
listView.setSelectionFromTop(index + (listView.getChildCount() < position ? 1 : 0), top);
}
@Override
public void discard() {
if (!swipedConversation.isRead()
&& swipedConversation.getMode() == Conversation.MODE_SINGLE) {
swipedConversation = null;
return;
}
endConversation(swipedConversation, false, false);
swipedConversation = null;
}
@Override
public String getTitle() {
if (swipedConversation.getMode() == Conversation.MODE_MULTI) {
return getResources().getString(R.string.title_undo_swipe_out_muc);
} else {
return getResources().getString(R.string.title_undo_swipe_out_conversation);
}
}
};
}
});
listView.enableSwipeToDismiss();
listView.setSwipingLayout(R.id.swipeable_item);
listView.setUndoStyle(EnhancedListView.UndoStyle.SINGLE_POPUP);
listView.setUndoHideDelay(5000);
listView.setRequireTouchBeforeDismiss(false);
mContentView = findViewById(R.id.content_view_spl);
if (mContentView == null) {
mContentView = findViewById(R.id.content_view_ll);
@ -204,6 +275,7 @@ public class ConversationActivity extends XmppActivity
@Override
public void onPanelClosed(View arg0) {
listView.discardUndo();
openConversation();
}
@ -303,7 +375,7 @@ public class ConversationActivity extends XmppActivity
if (this.getSelectedConversation().getLatestMessage()
.getEncryption() != Message.ENCRYPTION_NONE) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
menuSecure.setIcon(R.drawable.ic_lock_outline_white_48dp);
menuSecure.setIcon(R.drawable.ic_lock_white_24dp);
} else {
menuSecure.setIcon(R.drawable.ic_action_secure);
}
@ -340,13 +412,18 @@ public class ConversationActivity extends XmppActivity
switch (attachmentChoice) {
case ATTACHMENT_CHOICE_CHOOSE_IMAGE:
intent.setAction(Intent.ACTION_GET_CONTENT);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE,true);
}
intent.setType("image/*");
chooser = true;
break;
case ATTACHMENT_CHOICE_TAKE_PHOTO:
mPendingImageUri = xmppConnectionService.getFileBackend().getTakePhotoUri();
Uri uri = xmppConnectionService.getFileBackend().getTakePhotoUri();
intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
intent.putExtra(MediaStore.EXTRA_OUTPUT, mPendingImageUri);
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
mPendingImageUris.clear();
mPendingImageUris.add(uri);
break;
case ATTACHMENT_CHOICE_CHOOSE_FILE:
chooser = true;
@ -485,13 +562,21 @@ public class ConversationActivity extends XmppActivity
}
public void endConversation(Conversation conversation) {
showConversationsOverview();
endConversation(conversation, true, true);
}
public void endConversation(Conversation conversation, boolean showOverview, boolean reinit) {
if (showOverview) {
showConversationsOverview();
}
xmppConnectionService.archiveConversation(conversation);
if (conversationList.size() > 0) {
setSelectedConversation(conversationList.get(0));
this.mConversationFragment.reInit(getSelectedConversation());
} else {
setSelectedConversation(null);
if (reinit) {
if (conversationList.size() > 0) {
setSelectedConversation(conversationList.get(0));
this.mConversationFragment.reInit(getSelectedConversation());
} else {
setSelectedConversation(null);
}
}
}
@ -744,6 +829,7 @@ public class ConversationActivity extends XmppActivity
@Override
public void onPause() {
listView.discardUndo();
super.onPause();
this.mActivityPaused = true;
if (this.xmppConnectionServiceBound) {
@ -779,8 +865,8 @@ public class ConversationActivity extends XmppActivity
}
savedInstanceState.putBoolean(STATE_PANEL_OPEN,
isConversationsOverviewVisable());
if (this.mPendingImageUri != null) {
savedInstanceState.putString(STATE_PENDING_URI, this.mPendingImageUri.toString());
if (this.mPendingImageUris.size() >= 1) {
savedInstanceState.putString(STATE_PENDING_URI, this.mPendingImageUris.get(0).toString());
}
super.onSaveInstanceState(savedInstanceState);
}
@ -819,21 +905,23 @@ public class ConversationActivity extends XmppActivity
this.mConversationFragment.reInit(getSelectedConversation());
} else {
showConversationsOverview();
mPendingImageUri = null;
mPendingFileUri = null;
mPendingImageUris.clear();
mPendingFileUris.clear();
mPendingGeoUri = null;
setSelectedConversation(conversationList.get(0));
this.mConversationFragment.reInit(getSelectedConversation());
}
if (mPendingImageUri != null) {
attachImageToConversation(getSelectedConversation(),mPendingImageUri);
mPendingImageUri = null;
} else if (mPendingFileUri != null) {
attachFileToConversation(getSelectedConversation(),mPendingFileUri);
mPendingFileUri = null;
} else if (mPendingGeoUri != null) {
attachLocationToConversation(getSelectedConversation(),mPendingGeoUri);
for(Iterator<Uri> i = mPendingImageUris.iterator(); i.hasNext(); i.remove()) {
attachImageToConversation(getSelectedConversation(),i.next());
}
for(Iterator<Uri> i = mPendingFileUris.iterator(); i.hasNext(); i.remove()) {
attachFileToConversation(getSelectedConversation(),i.next());
}
if (mPendingGeoUri != null) {
attachLocationToConversation(getSelectedConversation(), mPendingGeoUri);
mPendingGeoUri = null;
}
ExceptionHelper.checkForCrash(this, this.xmppConnectionService);
@ -841,10 +929,10 @@ public class ConversationActivity extends XmppActivity
}
private void handleViewConversationIntent(final Intent intent) {
final String uuid = (String) intent.getExtras().get(CONVERSATION);
final String downloadUuid = (String) intent.getExtras().get(MESSAGE);
final String text = intent.getExtras().getString(TEXT, "");
final String nick = intent.getExtras().getString(NICK, null);
final String uuid = intent.getStringExtra(CONVERSATION);
final String downloadUuid = intent.getStringExtra(MESSAGE);
final String text = intent.getStringExtra(TEXT);
final String nick = intent.getStringExtra(NICK);
if (selectConversationByUuid(uuid)) {
this.mConversationFragment.reInit(getSelectedConversation());
if (nick != null) {
@ -885,6 +973,21 @@ public class ConversationActivity extends XmppActivity
xmppConnectionService.getNotificationService().setOpenConversation(null);
}
@SuppressLint("NewApi")
private static List<Uri> extractUriFromIntent(final Intent intent) {
List<Uri> uris = new ArrayList<>();
Uri uri = intent.getData();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2 && uri == null) {
ClipData clipData = intent.getClipData();
for(int i = 0; i < clipData.getItemCount(); ++i) {
uris.add(clipData.getItemAt(i).getUri());
}
} else {
uris.add(uri);
}
return uris;
}
@Override
protected void onActivityResult(int requestCode, int resultCode,
final Intent data) {
@ -894,25 +997,34 @@ public class ConversationActivity extends XmppActivity
mConversationFragment.hideSnackbar();
mConversationFragment.updateMessages();
} else if (requestCode == ATTACHMENT_CHOICE_CHOOSE_IMAGE) {
mPendingImageUri = data.getData();
mPendingImageUris.clear();
mPendingImageUris.addAll(extractUriFromIntent(data));
if (xmppConnectionServiceBound) {
attachImageToConversation(getSelectedConversation(),mPendingImageUri);
mPendingImageUri = null;
for(Iterator<Uri> i = mPendingImageUris.iterator(); i.hasNext(); i.remove()) {
attachImageToConversation(getSelectedConversation(),i.next());
}
}
} else if (requestCode == ATTACHMENT_CHOICE_CHOOSE_FILE || requestCode == ATTACHMENT_CHOICE_RECORD_VOICE) {
mPendingFileUri = data.getData();
mPendingFileUris.clear();
mPendingFileUris.addAll(extractUriFromIntent(data));
if (xmppConnectionServiceBound) {
attachFileToConversation(getSelectedConversation(),mPendingFileUri);
mPendingFileUri = null;
for(Iterator<Uri> i = mPendingImageUris.iterator(); i.hasNext(); i.remove()) {
attachFileToConversation(getSelectedConversation(), i.next());
}
}
} else if (requestCode == ATTACHMENT_CHOICE_TAKE_PHOTO && mPendingImageUri != null) {
if (xmppConnectionServiceBound) {
attachImageToConversation(getSelectedConversation(),mPendingImageUri);
mPendingImageUri = null;
} else if (requestCode == ATTACHMENT_CHOICE_TAKE_PHOTO) {
if (mPendingImageUris.size() == 1) {
Uri uri = mPendingImageUris.get(0);
if (xmppConnectionServiceBound) {
attachImageToConversation(getSelectedConversation(), uri);
mPendingImageUris.clear();
}
Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
intent.setData(uri);
sendBroadcast(intent);
} else {
mPendingImageUris.clear();
}
Intent intent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
intent.setData(mPendingImageUri);
sendBroadcast(intent);
} else if (requestCode == ATTACHMENT_CHOICE_LOCATION) {
double latitude = data.getDoubleExtra("latitude",0);
double longitude = data.getDoubleExtra("longitude",0);
@ -923,9 +1035,8 @@ public class ConversationActivity extends XmppActivity
}
}
} else {
if (requestCode == ATTACHMENT_CHOICE_TAKE_PHOTO) {
mPendingImageUri = null;
}
mPendingImageUris.clear();
mPendingFileUris.clear();
}
}
@ -1013,6 +1124,13 @@ public class ConversationActivity extends XmppActivity
public void updateConversationList() {
xmppConnectionService
.populateWithOrderedConversations(conversationList);
if (swipedConversation != null) {
if (swipedConversation.isRead()) {
conversationList.remove(swipedConversation);
} else {
listView.discardUndo();
}
}
listAdapter.notifyDataSetChanged();
}

View file

@ -8,7 +8,6 @@ import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentSender;
import android.content.IntentSender.SendIntentException;
import android.net.Uri;
import android.os.Bundle;
import android.text.InputType;
import android.view.ContextMenu;
@ -268,7 +267,6 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
if (conversation.getNextCounterpart() != null) {
message.setCounterpart(conversation.getNextCounterpart());
message.setType(Message.TYPE_PRIVATE);
conversation.setNextCounterpart(null);
}
}
if (conversation.getNextEncryption(activity.forceEncryption()) == Message.ENCRYPTION_OTR) {
@ -316,8 +314,8 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
@Override
public View onCreateView(final LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
final View view = inflater.inflate(R.layout.fragment_conversation,
container, false);
final View view = inflater.inflate(R.layout.fragment_conversation,container, false);
view.setOnClickListener(null);
mEditMessage = (EditMessage) view.findViewById(R.id.textinput);
setupIme();
mEditMessage.setOnClickListener(new OnClickListener() {
@ -720,21 +718,6 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
final ConversationActivity activity = (ConversationActivity) getActivity();
if (this.conversation != null) {
updateSnackBar(this.conversation);
final Contact contact = this.conversation.getContact();
if (this.conversation.isBlocked()) {
} else if (!contact.showInRoster()
&& contact
.getOption(Contact.Options.PENDING_SUBSCRIPTION_REQUEST)) {
} else if (conversation.getMode() == Conversation.MODE_SINGLE) {
makeFingerprintWarning();
} else if (!conversation.getMucOptions().online()
&& conversation.getAccount().getStatus() == Account.State.ONLINE) {
} else if (this.conversation.isMuted()) {
}
conversation.populateWithMessages(ConversationFragment.this.messageList);
for (final Message message : this.messageList) {
if (message.getEncryption() == Message.ENCRYPTION_PGP
@ -781,6 +764,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
} catch (final NoSuchElementException ignored) {
}
askForPassphraseIntent = null;
activity.xmppConnectionService.updateMessage(message);
}
@ -880,10 +864,6 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
}
}
protected void makeFingerprintWarning() {
}
protected void showSnackbar(final int message, final int action,
final OnClickListener clickListener) {
snackbar.setVisibility(View.VISIBLE);
@ -1020,6 +1000,9 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
}
public void appendText(String text) {
if (text == null) {
return;
}
String previous = this.mEditMessage.getText().toString();
if (previous.length() != 0 && !previous.endsWith(" ")) {
text = " " + text;

View file

@ -67,7 +67,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
@Override
public void onClick(final View v) {
if (mAccount != null && mAccount.getStatus() == Account.State.DISABLED) {
if (mAccount != null && mAccount.getStatus() == Account.State.DISABLED && !accountInfoEdited()) {
mAccount.setOption(Account.OPTION_DISABLED, false);
xmppConnectionService.updateAccount(mAccount);
return;
@ -237,7 +237,11 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
}
protected void updateSaveButton() {
if (mAccount != null && (mAccount.getStatus() == Account.State.CONNECTING || mFetchingAvatar)) {
if (accountInfoEdited() && jidToEdit != null) {
this.mSaveButton.setText(R.string.save);
this.mSaveButton.setEnabled(true);
this.mSaveButton.setTextColor(getPrimaryTextColor());
} else if (mAccount != null && (mAccount.getStatus() == Account.State.CONNECTING || mFetchingAvatar)) {
this.mSaveButton.setEnabled(false);
this.mSaveButton.setTextColor(getSecondaryTextColor());
this.mSaveButton.setText(R.string.account_status_connecting);
@ -265,9 +269,9 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
}
protected boolean accountInfoEdited() {
return (!this.mAccount.getJid().toBareJid().toString().equals(
this.mAccountJid.getText().toString()))
|| (!this.mAccount.getPassword().equals(
return this.mAccount != null && (!this.mAccount.getJid().toBareJid().toString().equals(
this.mAccountJid.getText().toString())
|| !this.mAccount.getPassword().equals(
this.mPassword.getText().toString()));
}
@ -464,7 +468,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
} else {
this.mServerInfoSm.setText(R.string.server_info_unavailable);
}
if (features.pubsub()) {
if (features.pep()) {
this.mServerInfoPep.setText(R.string.server_info_available);
} else {
this.mServerInfoPep.setText(R.string.server_info_unavailable);

View file

@ -168,6 +168,14 @@ public class ManageAccountActivity extends XmppActivity implements OnAccountUpda
}
}
public void onClickTglAccountState(Account account, boolean enable) {
if (enable) {
enableAccount(account);
} else {
disableAccount(account);
}
}
private void publishAvatar(Account account) {
Intent intent = new Intent(getApplicationContext(),
PublishProfilePictureActivity.class);

View file

@ -163,8 +163,7 @@ public class PublishProfilePictureActivity extends XmppActivity {
if (jid != null) {
this.account = xmppConnectionService.findAccountByJid(jid);
if (this.account.getXmppConnection() != null) {
this.support = this.account.getXmppConnection()
.getFeatures().pubsub();
this.support = this.account.getXmppConnection().getFeatures().pep();
}
if (this.avatarUri == null) {
if (this.account.getAvatar() != null

View file

@ -1,17 +1,29 @@
package eu.siacs.conversations.ui;
import java.security.KeyStoreException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Locale;
import eu.siacs.conversations.entities.Account;
import de.duenndns.ssl.MemorizingTrustManager;
import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.xmpp.XmppConnection;
import android.app.AlertDialog;
import android.app.Fragment;
import android.app.FragmentManager;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.os.Build;
import android.os.Bundle;
import android.preference.ListPreference;
import android.preference.Preference;
import android.preference.PreferenceManager;
import android.widget.Toast;
public class SettingsActivity extends XmppActivity implements
OnSharedPreferenceChangeListener {
@ -20,9 +32,12 @@ public class SettingsActivity extends XmppActivity implements
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mSettingsFragment = new SettingsFragment();
getFragmentManager().beginTransaction()
.replace(android.R.id.content, mSettingsFragment).commit();
FragmentManager fm = getFragmentManager();
mSettingsFragment = (SettingsFragment) fm.findFragmentById(android.R.id.content);
if (mSettingsFragment == null || !mSettingsFragment.getClass().equals(SettingsFragment.class)) {
mSettingsFragment = new SettingsFragment();
fm.beginTransaction().replace(android.R.id.content, mSettingsFragment).commit();
}
}
@Override
@ -33,19 +48,78 @@ public class SettingsActivity extends XmppActivity implements
@Override
public void onStart() {
super.onStart();
PreferenceManager.getDefaultSharedPreferences(this)
.registerOnSharedPreferenceChangeListener(this);
ListPreference resources = (ListPreference) mSettingsFragment
.findPreference("resource");
PreferenceManager.getDefaultSharedPreferences(this).registerOnSharedPreferenceChangeListener(this);
ListPreference resources = (ListPreference) mSettingsFragment.findPreference("resource");
if (resources != null) {
ArrayList<CharSequence> entries = new ArrayList<CharSequence>(
Arrays.asList(resources.getEntries()));
entries.add(0, Build.MODEL);
resources.setEntries(entries.toArray(new CharSequence[entries
.size()]));
resources.setEntryValues(entries.toArray(new CharSequence[entries
.size()]));
ArrayList<CharSequence> entries = new ArrayList<>(Arrays.asList(resources.getEntries()));
if (!entries.contains(Build.MODEL)) {
entries.add(0, Build.MODEL);
resources.setEntries(entries.toArray(new CharSequence[entries.size()]));
resources.setEntryValues(entries.toArray(new CharSequence[entries.size()]));
}
}
final Preference removeCertsPreference = mSettingsFragment.findPreference("remove_trusted_certificates");
removeCertsPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
@Override
public boolean onPreferenceClick(Preference preference) {
final MemorizingTrustManager mtm = xmppConnectionService.getMemorizingTrustManager();
final ArrayList<String> aliases = Collections.list(mtm.getCertificates());
if (aliases.size() == 0) {
displayToast(getString(R.string.toast_no_trusted_certs));
return true;
}
final ArrayList selectedItems = new ArrayList<Integer>();
final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(SettingsActivity.this);
dialogBuilder.setTitle(getResources().getString(R.string.dialog_manage_certs_title));
dialogBuilder.setMultiChoiceItems(aliases.toArray(new CharSequence[aliases.size()]), null,
new DialogInterface.OnMultiChoiceClickListener() {
@Override
public void onClick(DialogInterface dialog, int indexSelected,
boolean isChecked) {
if (isChecked) {
selectedItems.add(indexSelected);
} else if (selectedItems.contains(indexSelected)) {
selectedItems.remove(Integer.valueOf(indexSelected));
}
if (selectedItems.size() > 0)
((AlertDialog) dialog).getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(true);
else {
((AlertDialog) dialog).getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(false);
}
}
});
dialogBuilder.setPositiveButton(
getResources().getString(R.string.dialog_manage_certs_positivebutton), new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
int count = selectedItems.size();
if (count > 0) {
for (int i = 0; i < count; i++) {
try {
Integer item = Integer.valueOf(selectedItems.get(i).toString());
String alias = aliases.get(item);
mtm.deleteCertificate(alias);
} catch (KeyStoreException e) {
e.printStackTrace();
displayToast("Error: " + e.getLocalizedMessage());
}
}
if (xmppConnectionServiceBound) {
reconnectAccounts();
}
displayToast(getResources().getQuantityString(R.plurals.toast_delete_certificates, count, count));
}
}
});
dialogBuilder.setNegativeButton(getResources().getString(R.string.dialog_manage_certs_negativebutton), null);
AlertDialog removeCertsDialog = dialogBuilder.create();
removeCertsDialog.show();
removeCertsDialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
return true;
}
});
}
@Override
@ -63,9 +137,14 @@ public class SettingsActivity extends XmppActivity implements
.toLowerCase(Locale.US);
if (xmppConnectionServiceBound) {
for (Account account : xmppConnectionService.getAccounts()) {
account.setResource(resource);
if (!account.isOptionSet(Account.OPTION_DISABLED)) {
xmppConnectionService.reconnectAccountInBackground(account);
if (account.setResource(resource)) {
if (!account.isOptionSet(Account.OPTION_DISABLED)) {
XmppConnection connection = account.getXmppConnection();
if (connection != null) {
connection.resetStreamId();
}
xmppConnectionService.reconnectAccountInBackground(account);
}
}
}
}
@ -79,6 +158,27 @@ public class SettingsActivity extends XmppActivity implements
}
}
}
} else if (name.equals("dont_trust_system_cas")) {
xmppConnectionService.updateMemorizingTrustmanager();
reconnectAccounts();
}
}
private void displayToast(final String msg) {
runOnUiThread(new Runnable() {
@Override
public void run() {
Toast.makeText(SettingsActivity.this, msg, Toast.LENGTH_LONG).show();
}
});
}
private void reconnectAccounts() {
for (Account account : xmppConnectionService.getAccounts()) {
if (!account.isOptionSet(Account.OPTION_DISABLED)) {
xmppConnectionService.reconnectAccountInBackground(account);
}
}
}

View file

@ -18,6 +18,7 @@ import java.net.URLConnection;
import java.net.URLDecoder;
import java.nio.charset.UnsupportedCharsetException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import eu.siacs.conversations.Config;
@ -32,7 +33,7 @@ import eu.siacs.conversations.xmpp.jid.Jid;
public class ShareWithActivity extends XmppActivity {
private class Share {
public Uri uri;
public List<Uri> uris = new ArrayList<>();
public boolean image;
public String account;
public String contact;
@ -104,7 +105,7 @@ public class ShareWithActivity extends XmppActivity {
int position, long arg3) {
Conversation conversation = mConversations.get(position);
if (conversation.getMode() == Conversation.MODE_SINGLE
|| share.uri == null) {
|| share.uris.size() == 0) {
share(mConversations.get(position));
}
}
@ -133,18 +134,32 @@ public class ShareWithActivity extends XmppActivity {
@Override
public void onStart() {
final String type = getIntent().getType();
final Uri uri = getIntent().getParcelableExtra(Intent.EXTRA_STREAM);
if (type != null && uri != null && !type.equalsIgnoreCase("text/plain")) {
this.share.uri = uri;
this.share.image = type.startsWith("image/") || isImage(uri);
} else {
this.share.text = getIntent().getStringExtra(Intent.EXTRA_TEXT);
super.onStart();
Intent intent = getIntent();
if (intent == null) {
return;
}
final String type = intent.getType();
if (Intent.ACTION_SEND.equals(intent.getAction())) {
final Uri uri = getIntent().getParcelableExtra(Intent.EXTRA_STREAM);
if (type != null && uri != null && !type.equalsIgnoreCase("text/plain")) {
this.share.uris.add(uri);
this.share.image = type.startsWith("image/") || isImage(uri);
} else {
this.share.text = getIntent().getStringExtra(Intent.EXTRA_TEXT);
}
} else if (Intent.ACTION_SEND_MULTIPLE.equals(intent.getAction())) {
this.share.image = type != null && type.startsWith("image/");
if (!this.share.image) {
return;
}
this.share.uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM);
}
if (xmppConnectionServiceBound) {
xmppConnectionService.populateWithOrderedConversations(mConversations, this.share.uri == null);
xmppConnectionService.populateWithOrderedConversations(mConversations, this.share.image);
}
super.onStart();
}
protected boolean isImage(Uri uri) {
@ -164,7 +179,7 @@ public class ShareWithActivity extends XmppActivity {
return;
}
xmppConnectionService.populateWithOrderedConversations(mConversations,
this.share != null && this.share.uri == null);
this.share != null && this.share.uris.size() == 0);
}
private void share() {
@ -188,7 +203,7 @@ public class ShareWithActivity extends XmppActivity {
}
private void share(final Conversation conversation) {
if (share.uri != null) {
if (share.uris.size() != 0) {
selectPresence(conversation, new OnPresenceSelected() {
@Override
public void onPresenceSelected() {
@ -196,22 +211,23 @@ public class ShareWithActivity extends XmppActivity {
Toast.makeText(getApplicationContext(),
getText(R.string.preparing_image),
Toast.LENGTH_LONG).show();
ShareWithActivity.this.xmppConnectionService
.attachImageToConversation(conversation, share.uri,
attachFileCallback);
for (Iterator<Uri> i = share.uris.iterator(); i.hasNext(); i.remove()) {
ShareWithActivity.this.xmppConnectionService
.attachImageToConversation(conversation, i.next(),
attachFileCallback);
}
} else {
Toast.makeText(getApplicationContext(),
getText(R.string.preparing_file),
Toast.LENGTH_LONG).show();
ShareWithActivity.this.xmppConnectionService
.attachFileToConversation(conversation, share.uri,
attachFileCallback);
.attachFileToConversation(conversation, share.uris.get(0),
attachFileCallback);
}
switchToConversation(conversation, null, true);
finish();
}
});
} else {
switchToConversation(conversation, this.share.text, true);
finish();

View file

@ -90,6 +90,7 @@ public abstract class XmppActivity extends Activity {
protected int mPrimaryTextColor;
protected int mSecondaryTextColor;
protected int mPrimaryBackgroundColor;
protected int mSecondaryBackgroundColor;
protected int mColorRed;
protected int mColorOrange;
@ -331,6 +332,7 @@ public abstract class XmppActivity extends Activity {
mColorOrange = getResources().getColor(R.color.orange);
mColorGreen = getResources().getColor(R.color.green);
mPrimaryColor = getResources().getColor(R.color.primary);
mPrimaryBackgroundColor = getResources().getColor(R.color.primarybackground);
mSecondaryBackgroundColor = getResources().getColor(R.color.secondarybackground);
this.mTheme = findTheme();
setTheme(this.mTheme);
@ -740,7 +742,11 @@ public abstract class XmppActivity extends Activity {
public int getOnlineColor() {
return this.mColorGreen;
}
public int getPrimaryBackgroundColor() {
return this.mPrimaryBackgroundColor;
}
public int getSecondaryBackgroundColor() {
return this.mSecondaryBackgroundColor;
}

View file

@ -5,13 +5,16 @@ import java.util.List;
import eu.siacs.conversations.R;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.ui.XmppActivity;
import eu.siacs.conversations.ui.ManageAccountActivity;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.CompoundButton;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Switch;
public class AccountAdapter extends ArrayAdapter<Account> {
@ -24,7 +27,7 @@ public class AccountAdapter extends ArrayAdapter<Account> {
@Override
public View getView(int position, View view, ViewGroup parent) {
Account account = getItem(position);
final Account account = getItem(position);
if (view == null) {
LayoutInflater inflater = (LayoutInflater) getContext()
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
@ -34,21 +37,32 @@ public class AccountAdapter extends ArrayAdapter<Account> {
jid.setText(account.getJid().toBareJid().toString());
TextView statusView = (TextView) view.findViewById(R.id.account_status);
ImageView imageView = (ImageView) view.findViewById(R.id.account_image);
imageView.setImageBitmap(activity.avatarService().get(account,
activity.getPixel(48)));
statusView.setText(getContext().getString(account.getStatus().getReadableId()));
switch (account.getStatus()) {
case ONLINE:
statusView.setTextColor(activity.getOnlineColor());
break;
case DISABLED:
case CONNECTING:
statusView.setTextColor(activity.getSecondaryTextColor());
break;
default:
statusView.setTextColor(activity.getWarningTextColor());
break;
}
imageView.setImageBitmap(activity.avatarService().get(account, activity.getPixel(48)));
statusView.setText(getContext().getString(account.getStatus().getReadableId()));
switch (account.getStatus()) {
case ONLINE:
statusView.setTextColor(activity.getOnlineColor());
break;
case DISABLED:
case CONNECTING:
statusView.setTextColor(activity.getSecondaryTextColor());
break;
default:
statusView.setTextColor(activity.getWarningTextColor());
break;
}
final Switch tglAccountState = (Switch) view.findViewById(R.id.tgl_account_status);
final boolean isDisabled = (account.getStatus() == Account.State.DISABLED) ? true : false;
tglAccountState.setOnCheckedChangeListener(null);
tglAccountState.setChecked(!isDisabled);
tglAccountState.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
if (b == isDisabled && activity instanceof ManageAccountActivity) {
((ManageAccountActivity) activity).onClickTglAccountState(account,b);
}
}
});
return view;
}
}

View file

@ -46,17 +46,10 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> {
}
Conversation conversation = getItem(position);
if (this.activity instanceof ConversationActivity) {
ConversationActivity activity = (ConversationActivity) this.activity;
if (!activity.isConversationsOverviewHideable()) {
if (conversation == activity.getSelectedConversation()) {
view.setBackgroundColor(activity
.getSecondaryBackgroundColor());
} else {
view.setBackgroundColor(Color.TRANSPARENT);
}
} else {
view.setBackgroundColor(Color.TRANSPARENT);
}
View swipeableItem = view.findViewById(R.id.swipeable_item);
ConversationActivity a = (ConversationActivity) this.activity;
int c = !a.isConversationsOverviewHideable() && conversation == a.getSelectedConversation() ? a.getSecondaryBackgroundColor() : a.getPrimaryBackgroundColor();
swipeableItem.setBackgroundColor(c);
}
TextView convName = (TextView) view.findViewById(R.id.conversation_name);
if (conversation.getMode() == Conversation.MODE_SINGLE || activity.useSubjectToIdentifyConference()) {

View file

@ -91,6 +91,9 @@ public final class CryptoHelper {
}
public static String prettifyFingerprint(String fingerprint) {
if (fingerprint.length() < 40) {
return fingerprint;
}
StringBuilder builder = new StringBuilder(fingerprint);
builder.insert(8, " ");
builder.insert(17, " ");

View file

@ -20,7 +20,7 @@ public class GeoHelper {
}
public static ArrayList<Intent> createGeoIntentsFromMessage(Message message) {
final ArrayList<Intent> intents = new ArrayList();
final ArrayList<Intent> intents = new ArrayList<>();
Matcher matcher = GEO_URI.matcher(message.getBody());
if (!matcher.matches()) {
return intents;

View file

@ -90,7 +90,7 @@ public class XmppConnection implements Runnable {
private boolean shouldBind = true;
private boolean shouldAuthenticate = true;
private Element streamFeatures;
private final HashMap<String, List<String>> disco = new HashMap<>();
private final HashMap<Jid, Info> disco = new HashMap<>();
private String streamId = null;
private int smVersion = 3;
@ -334,16 +334,23 @@ public class XmppConnection implements Runnable {
} catch (final NumberFormatException ignored) {
}
sendServiceDiscoveryInfo(account.getServer());
sendServiceDiscoveryInfo(account.getJid().toBareJid());
sendServiceDiscoveryItems(account.getServer());
sendInitialPing();
} else if (nextTag.isStart("r")) {
tagReader.readElement(nextTag);
if (Config.EXTENDED_SM_LOGGING) {
Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": acknowledging stanza #" + this.stanzasReceived);
}
final AckPacket ack = new AckPacket(this.stanzasReceived, smVersion);
tagWriter.writeStanzaAsync(ack);
} else if (nextTag.isStart("a")) {
final Element ack = tagReader.readElement(nextTag);
lastPacketReceived = SystemClock.elapsedRealtime();
final int serverSequence = Integer.parseInt(ack.getAttribute("h"));
if (Config.EXTENDED_SM_LOGGING) {
Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": server acknowledged stanza #" + serverSequence);
}
final String msgId = this.messageReceipts.get(serverSequence);
if (msgId != null) {
if (this.acknowledgedListener != null) {
@ -597,8 +604,10 @@ public class XmppConnection implements Runnable {
} else if (this.streamFeatures.hasChild("sm", "urn:xmpp:sm:"
+ smVersion)
&& streamId != null) {
final ResumePacket resume = new ResumePacket(this.streamId,
stanzasReceived, smVersion);
if (Config.EXTENDED_SM_LOGGING) {
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": resuming after stanza #"+stanzasReceived);
}
final ResumePacket resume = new ResumePacket(this.streamId, stanzasReceived, smVersion);
this.tagWriter.writeStanzaAsync(resume);
} else if (this.streamFeatures.hasChild("bind") && shouldBind) {
sendBindRequest();
@ -734,6 +743,7 @@ public class XmppConnection implements Runnable {
features.blockListRequested = false;
disco.clear();
sendServiceDiscoveryInfo(account.getServer());
sendServiceDiscoveryInfo(account.getJid().toBareJid());
sendServiceDiscoveryItems(account.getServer());
if (bindListener != null) {
bindListener.onBind(account);
@ -741,34 +751,35 @@ public class XmppConnection implements Runnable {
sendInitialPing();
}
private void sendServiceDiscoveryInfo(final Jid server) {
if (disco.containsKey(server.toDomainJid().toString())) {
if (account.getServer().equals(server.toDomainJid())) {
private void sendServiceDiscoveryInfo(final Jid jid) {
if (disco.containsKey(jid)) {
if (account.getServer().equals(jid)) {
enableAdvancedStreamFeatures();
}
} else {
final IqPacket iq = new IqPacket(IqPacket.TYPE.GET);
iq.setTo(server.toDomainJid());
iq.setTo(jid);
iq.query("http://jabber.org/protocol/disco#info");
this.sendIqPacket(iq, new OnIqPacketReceived() {
@Override
public void onIqPacketReceived(final Account account, final IqPacket packet) {
final List<Element> elements = packet.query().getChildren();
final List<String> features = new ArrayList<>();
final Info info = new Info();
for (final Element element : elements) {
if (element.getName().equals("identity")) {
if ("irc".equals(element.getAttribute("type"))) {
//add fake feature to not confuse irc and real muc
features.add("siacs:no:muc");
String type = element.getAttribute("type");
String category = element.getAttribute("category");
if (type != null && category != null) {
info.identities.add(new Pair<>(category,type));
}
} else if (element.getName().equals("feature")) {
features.add(element.getAttribute("var"));
info.features.add(element.getAttribute("var"));
}
}
disco.put(server.toDomainJid().toString(), features);
disco.put(jid, info);
if (account.getServer().equals(server.toDomainJid())) {
if (account.getServer().equals(jid)) {
enableAdvancedStreamFeatures();
for (final OnAdvancedStreamFeaturesLoaded listener : advancedStreamFeaturesLoadedListeners) {
listener.onAdvancedStreamFeaturesAvailable(account);
@ -784,7 +795,7 @@ public class XmppConnection implements Runnable {
sendEnableCarbons();
}
if (getFeatures().blocking() && !features.blockListRequested) {
Log.d(Config.LOGTAG, "Requesting block list");
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": Requesting block list");
this.sendIqPacket(getIqGenerator().generateGetBlockList(), mXmppConnectionService.getIqParser());
}
}
@ -891,7 +902,9 @@ public class XmppConnection implements Runnable {
}
tagWriter.writeStanzaAsync(packet);
if (packet instanceof MessagePacket && packet.getId() != null && this.streamId != null) {
Log.d(Config.LOGTAG, "request delivery report for stanza " + stanzasSent);
if (Config.EXTENDED_SM_LOGGING) {
Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": requesting ack for message stanza #" + stanzasSent);
}
this.messageReceipts.put(stanzasSent, packet.getId());
tagWriter.writeStanzaAsync(new RequestPacket(this.smVersion));
}
@ -981,11 +994,15 @@ public class XmppConnection implements Runnable {
}
}
public void resetStreamId() {
this.streamId = null;
}
public List<String> findDiscoItemsByFeature(final String feature) {
final List<String> items = new ArrayList<>();
for (final Entry<String, List<String>> cursor : disco.entrySet()) {
if (cursor.getValue().contains(feature)) {
items.add(cursor.getKey());
for (final Entry<Jid, Info> cursor : disco.entrySet()) {
if (cursor.getValue().features.contains(feature)) {
items.add(cursor.getKey().toString());
}
}
return items;
@ -1004,10 +1021,12 @@ public class XmppConnection implements Runnable {
}
public String getMucServer() {
for (final Entry<String, List<String>> cursor : disco.entrySet()) {
final List<String> value = cursor.getValue();
if (value.contains("http://jabber.org/protocol/muc") && !value.contains("jabber:iq:gateway") && !value.contains("siacs:no:muc")) {
return cursor.getKey();
for (final Entry<Jid, Info> cursor : disco.entrySet()) {
final Info value = cursor.getValue();
if (value.features.contains("http://jabber.org/protocol/muc")
&& !value.features.contains("jabber:iq:gateway")
&& !value.identities.contains(new Pair<>("conference","irc"))) {
return cursor.getKey().toString();
}
}
return null;
@ -1062,6 +1081,11 @@ public class XmppConnection implements Runnable {
this.lastConnect = 0;
}
private class Info {
public final ArrayList<String> features = new ArrayList<>();
public final ArrayList<Pair<String,String>> identities = new ArrayList<>();
}
public class Features {
XmppConnection connection;
private boolean carbonsEnabled = false;
@ -1073,8 +1097,8 @@ public class XmppConnection implements Runnable {
}
private boolean hasDiscoFeature(final Jid server, final String feature) {
return connection.disco.containsKey(server.toDomainJid().toString()) &&
connection.disco.get(server.toDomainJid().toString()).contains(feature);
return connection.disco.containsKey(server) &&
connection.disco.get(server).features.contains(feature);
}
public boolean carbons() {
@ -1090,24 +1114,35 @@ public class XmppConnection implements Runnable {
}
public boolean sm() {
return streamId != null;
return streamId != null
|| (connection.streamFeatures != null && connection.streamFeatures.hasChild("sm"));
}
public boolean csi() {
return connection.streamFeatures != null && connection.streamFeatures.hasChild("csi", "urn:xmpp:csi:0");
}
public boolean pubsub() {
return hasDiscoFeature(account.getServer(),
"http://jabber.org/protocol/pubsub#publish");
public boolean pep() {
final Pair<String,String> needle = new Pair<>("pubsub","pep");
Info info = disco.get(account.getServer());
if (info != null && info.identities.contains(needle)) {
return true;
} else {
info = disco.get(account.getJid().toBareJid());
return info != null && info.identities.contains(needle);
}
}
public boolean mam() {
return hasDiscoFeature(account.getServer(), "urn:xmpp:mam:0");
if (hasDiscoFeature(account.getJid().toBareJid(), "urn:xmpp:mam:0")) {
return true;
} else {
return hasDiscoFeature(account.getServer(), "urn:xmpp:mam:0");
}
}
public boolean advancedStreamFeaturesLoaded() {
return disco.containsKey(account.getServer().toString());
return disco.containsKey(account.getServer());
}
public boolean rosterVersioning() {

View file

@ -192,7 +192,7 @@ public class JingleConnection implements Downloadable {
} else {
response = packet.generateResponse(IqPacket.TYPE.ERROR);
}
account.getXmppConnection().sendIqPacket(response, null);
mXmppConnectionService.sendIqPacket(account,response,null);
}
public void init(Message message) {
@ -317,7 +317,7 @@ public class JingleConnection implements Downloadable {
message.setBody(Long.toString(size));
conversation.add(message);
mXmppConnectionService.updateConversationUi();
if (size <= this.mJingleConnectionManager
if (size < this.mJingleConnectionManager
.getAutoAcceptFileSize()) {
Log.d(Config.LOGTAG, "auto accepting file from "
+ packet.getFrom());
@ -459,11 +459,11 @@ public class JingleConnection implements Downloadable {
}
private void sendJinglePacket(JinglePacket packet) {
account.getXmppConnection().sendIqPacket(packet, responseListener);
mXmppConnectionService.sendIqPacket(account,packet,responseListener);
}
private void sendJinglePacket(JinglePacket packet, OnIqPacketReceived callback) {
account.getXmppConnection().sendIqPacket(packet,callback);
mXmppConnectionService.sendIqPacket(account,packet,callback);
}
private boolean receiveAccept(JinglePacket packet) {
@ -556,7 +556,7 @@ public class JingleConnection implements Downloadable {
.setAttribute("sid", this.getSessionId());
activation.query().addChild("activate")
.setContent(this.getCounterPart().toString());
this.account.getXmppConnection().sendIqPacket(activation,
mXmppConnectionService.sendIqPacket(account,activation,
new OnIqPacketReceived() {
@Override

View file

@ -11,6 +11,7 @@ import android.util.Base64;
import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.DownloadableFile;
import eu.siacs.conversations.persistance.FileBackend;
import eu.siacs.conversations.utils.CryptoHelper;
import eu.siacs.conversations.xml.Element;
import eu.siacs.conversations.xmpp.OnIqPacketReceived;
@ -172,6 +173,7 @@ public class JingleInbandTransport extends JingleTransport {
connection.updateProgress((int) ((((double) (this.fileSize - this.remainingSize)) / this.fileSize) * 100));
}
} catch (IOException e) {
FileBackend.close(fileInputStream);
this.onFileTransmissionStatusChanged.onFileTransferAborted();
}
}
@ -198,6 +200,7 @@ public class JingleInbandTransport extends JingleTransport {
connection.updateProgress((int) ((((double) (this.fileSize - this.remainingSize)) / this.fileSize) * 100));
}
} catch (IOException e) {
FileBackend.close(fileOutputStream);
this.onFileTransmissionStatusChanged.onFileTransferAborted();
}
}
@ -207,6 +210,7 @@ public class JingleInbandTransport extends JingleTransport {
if (!established) {
established = true;
connected = true;
this.receiveNextBlock("");
this.account.getXmppConnection().sendIqPacket(
packet.generateResponse(IqPacket.TYPE.RESULT), null);
} else {

View file

@ -11,6 +11,7 @@ import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import eu.siacs.conversations.entities.DownloadableFile;
import eu.siacs.conversations.persistance.FileBackend;
import eu.siacs.conversations.utils.CryptoHelper;
public class JingleSocks5Transport extends JingleTransport {
@ -126,25 +127,19 @@ public class JingleSocks5Transport extends JingleTransport {
} catch (NoSuchAlgorithmException e) {
callback.onFileTransferAborted();
} finally {
try {
if (fileInputStream != null) {
fileInputStream.close();
}
} catch (IOException e) {
callback.onFileTransferAborted();
}
FileBackend.close(fileInputStream);
}
}
}).start();
}
public void receive(final DownloadableFile file,
final OnFileTransmissionStatusChanged callback) {
public void receive(final DownloadableFile file, final OnFileTransmissionStatusChanged callback) {
new Thread(new Runnable() {
@Override
public void run() {
OutputStream fileOutputStream = null;
try {
MessageDigest digest = MessageDigest.getInstance("SHA-1");
digest.reset();
@ -152,7 +147,7 @@ public class JingleSocks5Transport extends JingleTransport {
socket.setSoTimeout(30000);
file.getParentFile().mkdirs();
file.createNewFile();
OutputStream fileOutputStream = file.createOutputStream();
fileOutputStream = file.createOutputStream();
if (fileOutputStream == null) {
callback.onFileTransferAborted();
return;
@ -183,6 +178,8 @@ public class JingleSocks5Transport extends JingleTransport {
callback.onFileTransferAborted();
} catch (NoSuchAlgorithmException e) {
callback.onFileTransferAborted();
} finally {
FileBackend.close(fileOutputStream);
}
}
}).start();

Binary file not shown.

After

Width:  |  Height:  |  Size: 223 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 452 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 363 B

View file

Before

Width:  |  Height:  |  Size: 287 B

After

Width:  |  Height:  |  Size: 287 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 348 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 246 B

View file

Before

Width:  |  Height:  |  Size: 341 B

After

Width:  |  Height:  |  Size: 341 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 419 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 339 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 396 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 417 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 300 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 400 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 743 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 399 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 383 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 875 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 217 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 504 B

View file

Before

Width:  |  Height:  |  Size: 572 B

After

Width:  |  Height:  |  Size: 572 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 780 B

View file

Before

Width:  |  Height:  |  Size: 421 B

After

Width:  |  Height:  |  Size: 421 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 540 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 290 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 217 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 199 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 197 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 276 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 313 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 226 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 449 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 296 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 289 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 637 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 197 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 346 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 423 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 328 B

View file

Before

Width:  |  Height:  |  Size: 198 B

After

Width:  |  Height:  |  Size: 198 B

View file

Before

Width:  |  Height:  |  Size: 576 B

After

Width:  |  Height:  |  Size: 576 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 533 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 320 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 271 B

View file

Before

Width:  |  Height:  |  Size: 270 B

After

Width:  |  Height:  |  Size: 270 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 379 B

View file

Before

Width:  |  Height:  |  Size: 378 B

After

Width:  |  Height:  |  Size: 378 B

View file

Before

Width:  |  Height:  |  Size: 484 B

After

Width:  |  Height:  |  Size: 484 B

View file

Before

Width:  |  Height:  |  Size: 464 B

After

Width:  |  Height:  |  Size: 464 B

View file

Before

Width:  |  Height:  |  Size: 330 B

After

Width:  |  Height:  |  Size: 330 B

View file

Before

Width:  |  Height:  |  Size: 513 B

After

Width:  |  Height:  |  Size: 513 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 948 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 465 B

View file

Before

Width:  |  Height:  |  Size: 423 B

After

Width:  |  Height:  |  Size: 423 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 212 B

View file

Before

Width:  |  Height:  |  Size: 591 B

After

Width:  |  Height:  |  Size: 591 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 704 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 460 B

View file

Before

Width:  |  Height:  |  Size: 222 B

After

Width:  |  Height:  |  Size: 222 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 356 B

View file

Before

Width:  |  Height:  |  Size: 870 B

After

Width:  |  Height:  |  Size: 870 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

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