Merge tag '1.3.0'
|
@ -1,5 +1,13 @@
|
||||||
###Changelog
|
###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
|
####Version 1.2.0
|
||||||
* Send current location. (requires [plugin](https://play.google.com/store/apps/details?id=eu.siacs.conversations.sharelocation))
|
* Send current location. (requires [plugin](https://play.google.com/store/apps/details?id=eu.siacs.conversations.sharelocation))
|
||||||
* Invite multiple contacts at once
|
* Invite multiple contacts at once
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
Conversations: the very last word in instant messaging
|
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)
|
![screenshots](https://raw.githubusercontent.com/siacs/Conversations/master/screenshots.png)
|
||||||
|
|
||||||
|
@ -17,7 +17,8 @@ Conversations: the very last word in instant messaging
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
* End-to-end encryption with either [OTR](https://otr.cypherpunks.ca/) or [OpenPGP](http://www.openpgp.org/about_openpgp/)
|
* 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
|
* Indication when your contact has read your message
|
||||||
* Intuitive UI that follows Android Design guidelines
|
* Intuitive UI that follows Android Design guidelines
|
||||||
* Pictures / Avatars for your Contacts
|
* Pictures / Avatars for your Contacts
|
||||||
|
|
|
@ -34,6 +34,7 @@ dependencies {
|
||||||
compile 'com.google.zxing:core:3.1.0'
|
compile 'com.google.zxing:core:3.1.0'
|
||||||
compile 'com.google.zxing:android-integration:3.1.0'
|
compile 'com.google.zxing:android-integration:3.1.0'
|
||||||
compile 'de.measite.minidns:minidns:0.1.3'
|
compile 'de.measite.minidns:minidns:0.1.3'
|
||||||
|
compile 'de.timroes.android:EnhancedListView:0.3.4'
|
||||||
}
|
}
|
||||||
|
|
||||||
android {
|
android {
|
||||||
|
@ -43,8 +44,8 @@ android {
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 14
|
minSdkVersion 14
|
||||||
targetSdkVersion 21
|
targetSdkVersion 21
|
||||||
versionCode 56
|
versionCode 60
|
||||||
versionName "1.2.0"
|
versionName "1.3.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
|
|
|
@ -122,6 +122,13 @@
|
||||||
|
|
||||||
<data android:mimeType="*/*" />
|
<data android:mimeType="*/*" />
|
||||||
</intent-filter>
|
</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>
|
||||||
<activity
|
<activity
|
||||||
android:name="de.duenndns.ssl.MemorizingActivity"
|
android:name="de.duenndns.ssl.MemorizingActivity"
|
||||||
|
|
|
@ -28,6 +28,7 @@ public final class Config {
|
||||||
|
|
||||||
public static final boolean NO_PROXY_LOOKUP = false; //useful to debug ibb
|
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 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 MILLISECONDS_IN_DAY = 24 * 60 * 60 * 1000;
|
||||||
public static final long MAM_MAX_CATCHUP = MILLISECONDS_IN_DAY / 2;
|
public static final long MAM_MAX_CATCHUP = MILLISECONDS_IN_DAY / 2;
|
||||||
|
|
|
@ -182,7 +182,7 @@ public class OtrEngine extends OtrCryptoEngineImpl implements OtrEngineHost {
|
||||||
packet.setBody(body);
|
packet.setBody(body);
|
||||||
packet.addChild("private", "urn:xmpp:carbons:2");
|
packet.addChild("private", "urn:xmpp:carbons:2");
|
||||||
packet.addChild("no-copy", "urn:xmpp:hints");
|
packet.addChild("no-copy", "urn:xmpp:hints");
|
||||||
packet.addChild("no-store", "urn:xmpp:hints");
|
packet.addChild("no-permanent-store", "urn:xmpp:hints");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Jid jid = Jid.fromSessionID(session);
|
Jid jid = Jid.fromSessionID(session);
|
||||||
|
@ -202,20 +202,7 @@ public class OtrEngine extends OtrCryptoEngineImpl implements OtrEngineHost {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void messageFromAnotherInstanceReceived(SessionID session) {
|
public void messageFromAnotherInstanceReceived(SessionID session) {
|
||||||
try {
|
sendOtrErrorMessage(session, "Message from another OTR-instance received");
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -267,9 +254,28 @@ public class OtrEngine extends OtrCryptoEngineImpl implements OtrEngineHost {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void unreadableMessageReceived(SessionID arg0) throws OtrException {
|
public void unreadableMessageReceived(SessionID session) throws OtrException {
|
||||||
Log.d(Config.LOGTAG,"unreadable message received");
|
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
|
@Override
|
||||||
|
|
|
@ -229,11 +229,17 @@ public class Account extends AbstractEntity {
|
||||||
return jid.getResourcepart();
|
return jid.getResourcepart();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setResource(final String resource) {
|
public boolean setResource(final String resource) {
|
||||||
try {
|
final String oldResource = jid.getResourcepart();
|
||||||
jid = Jid.fromParts(jid.getLocalpart(), jid.getDomainpart(), resource);
|
if (oldResource == null || !oldResource.equals(resource)) {
|
||||||
} catch (final InvalidJidException ignored) {
|
try {
|
||||||
|
jid = Jid.fromParts(jid.getLocalpart(), jid.getDomainpart(), resource);
|
||||||
|
return true;
|
||||||
|
} catch (final InvalidJidException ignored) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Jid getJid() {
|
public Jid getJid() {
|
||||||
|
|
|
@ -430,23 +430,31 @@ public class Message extends AbstractEntity {
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean bodyContainsDownloadable() {
|
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 {
|
try {
|
||||||
URL url = new URL(this.getBody());
|
URL url = new URL(body);
|
||||||
if (!url.getProtocol().equalsIgnoreCase("http")
|
if (!url.getProtocol().equalsIgnoreCase("http")
|
||||||
&& !url.getProtocol().equalsIgnoreCase("https")) {
|
&& !url.getProtocol().equalsIgnoreCase("https")) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (url.getPath() == null) {
|
|
||||||
|
String sUrlPath = url.getPath();
|
||||||
|
if (sUrlPath == null || sUrlPath.isEmpty()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
String[] pathParts = url.getPath().split("/");
|
|
||||||
String filename;
|
int iSlashIndex = sUrlPath.lastIndexOf('/') + 1;
|
||||||
if (pathParts.length > 0) {
|
|
||||||
filename = pathParts[pathParts.length - 1].toLowerCase();
|
String sLastUrlPath = sUrlPath.substring(iSlashIndex).toLowerCase();
|
||||||
} else {
|
|
||||||
return false;
|
String[] extensionParts = sLastUrlPath.split("\\.");
|
||||||
}
|
|
||||||
String[] extensionParts = filename.split("\\.");
|
|
||||||
if (extensionParts.length == 2
|
if (extensionParts.length == 2
|
||||||
&& Arrays.asList(Downloadable.VALID_IMAGE_EXTENSIONS).contains(
|
&& Arrays.asList(Downloadable.VALID_IMAGE_EXTENSIONS).contains(
|
||||||
extensionParts[extensionParts.length - 1])) {
|
extensionParts[extensionParts.length - 1])) {
|
||||||
|
|
|
@ -71,6 +71,7 @@ public class MessageGenerator extends AbstractGenerator {
|
||||||
MessagePacket packet = preparePacket(message, addDelay);
|
MessagePacket packet = preparePacket(message, addDelay);
|
||||||
packet.addChild("private", "urn:xmpp:carbons:2");
|
packet.addChild("private", "urn:xmpp:carbons:2");
|
||||||
packet.addChild("no-copy", "urn:xmpp:hints");
|
packet.addChild("no-copy", "urn:xmpp:hints");
|
||||||
|
packet.addChild("no-permanent-store", "urn:xmpp:hints");
|
||||||
try {
|
try {
|
||||||
packet.setBody(otrSession.transformSending(message.getBody())[0]);
|
packet.setBody(otrSession.transformSending(message.getBody())[0]);
|
||||||
return packet;
|
return packet;
|
||||||
|
@ -172,7 +173,7 @@ public class MessageGenerator extends AbstractGenerator {
|
||||||
return receivedPacket;
|
return receivedPacket;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MessagePacket generateOtrError(Jid to, String id) {
|
public MessagePacket generateOtrError(Jid to, String id, String errorText) {
|
||||||
MessagePacket packet = new MessagePacket();
|
MessagePacket packet = new MessagePacket();
|
||||||
packet.setType(MessagePacket.TYPE_ERROR);
|
packet.setType(MessagePacket.TYPE_ERROR);
|
||||||
packet.setAttribute("id",id);
|
packet.setAttribute("id",id);
|
||||||
|
@ -181,7 +182,7 @@ public class MessageGenerator extends AbstractGenerator {
|
||||||
error.setAttribute("code","406");
|
error.setAttribute("code","406");
|
||||||
error.setAttribute("type","modify");
|
error.setAttribute("type","modify");
|
||||||
error.addChild("not-acceptable","urn:ietf:params:xml:ns:xmpp-stanzas");
|
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;
|
return packet;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,4 +54,11 @@ public class PresenceGenerator extends AbstractGenerator {
|
||||||
}
|
}
|
||||||
return packet;
|
return packet;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PresencePacket sendOfflinePresence(Account account) {
|
||||||
|
PresencePacket packet = new PresencePacket();
|
||||||
|
packet.setFrom(account.getJid());
|
||||||
|
packet.setAttribute("type","unavailable");
|
||||||
|
return packet;
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -391,15 +391,17 @@ public class MessageParser extends AbstractParser implements
|
||||||
|
|
||||||
private void parseNonMessage(Element packet, Account account) {
|
private void parseNonMessage(Element packet, Account account) {
|
||||||
final Jid from = packet.getAttributeAsJid("from");
|
final Jid from = packet.getAttributeAsJid("from");
|
||||||
|
if (account.getJid().equals(from)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (extractChatState(from == null ? null : mXmppConnectionService.find(account,from), packet)) {
|
if (extractChatState(from == null ? null : mXmppConnectionService.find(account,from), packet)) {
|
||||||
mXmppConnectionService.updateConversationUi();
|
mXmppConnectionService.updateConversationUi();
|
||||||
}
|
}
|
||||||
Element invite = extractInvite(packet);
|
Invite invite = extractInvite(packet);
|
||||||
if (invite != null) {
|
if (invite != null && invite.jid != null) {
|
||||||
Conversation conversation = mXmppConnectionService.findOrCreateConversation(account, from, true);
|
Conversation conversation = mXmppConnectionService.findOrCreateConversation(account, invite.jid, true);
|
||||||
if (!conversation.getMucOptions().online()) {
|
if (!conversation.getMucOptions().online()) {
|
||||||
Element password = invite.findChild("password");
|
conversation.getMucOptions().setPassword(invite.password);
|
||||||
conversation.getMucOptions().setPassword(password == null ? null : password.getContent());
|
|
||||||
mXmppConnectionService.databaseBackend.updateConversation(conversation);
|
mXmppConnectionService.databaseBackend.updateConversation(conversation);
|
||||||
mXmppConnectionService.joinMuc(conversation);
|
mXmppConnectionService.joinMuc(conversation);
|
||||||
mXmppConnectionService.updateConversationUi();
|
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");
|
Element x = message.findChild("x","http://jabber.org/protocol/muc#user");
|
||||||
if (x == null) {
|
if (x != null) {
|
||||||
x = message.findChild("x","jabber:x:conference");
|
Element invite = x.findChild("invite");
|
||||||
}
|
if (invite != null) {
|
||||||
if (x != null && x.hasChild("invite")) {
|
Element pw = x.findChild("password");
|
||||||
return x;
|
return new Invite(message.getAttributeAsJid("from"), pw != null ? pw.getContent(): null);
|
||||||
|
}
|
||||||
} else {
|
} 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) {
|
private void parseEvent(final Element event, final Jid from, final Account account) {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package eu.siacs.conversations.persistance;
|
package eu.siacs.conversations.persistance;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
|
import java.io.Closeable;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
|
@ -42,8 +43,7 @@ public class FileBackend {
|
||||||
|
|
||||||
private static int IMAGE_SIZE = 1920;
|
private static int IMAGE_SIZE = 1920;
|
||||||
|
|
||||||
private SimpleDateFormat imageDateFormat = new SimpleDateFormat(
|
private final SimpleDateFormat imageDateFormat = new SimpleDateFormat("yyyyMMdd_HHmmssSSS", Locale.US);
|
||||||
"yyyyMMdd_HHmmssSSS", Locale.US);
|
|
||||||
|
|
||||||
private XmppConnectionService mXmppConnectionService;
|
private XmppConnectionService mXmppConnectionService;
|
||||||
|
|
||||||
|
@ -110,9 +110,7 @@ public class FileBackend {
|
||||||
scalledW = size;
|
scalledW = size;
|
||||||
scalledH = (int) (h / ((double) w / size));
|
scalledH = (int) (h / ((double) w / size));
|
||||||
}
|
}
|
||||||
Bitmap scalledBitmap = Bitmap.createScaledBitmap(originalBitmap,
|
return Bitmap.createScaledBitmap(originalBitmap, scalledW, scalledH, true);
|
||||||
scalledW, scalledH, true);
|
|
||||||
return scalledBitmap;
|
|
||||||
} else {
|
} else {
|
||||||
return originalBitmap;
|
return originalBitmap;
|
||||||
}
|
}
|
||||||
|
@ -148,31 +146,35 @@ public class FileBackend {
|
||||||
}
|
}
|
||||||
|
|
||||||
public DownloadableFile copyFileToPrivateStorage(Message message, Uri uri) throws FileCopyException {
|
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 {
|
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();
|
file.createNewFile();
|
||||||
OutputStream os = new FileOutputStream(file);
|
os = new FileOutputStream(file);
|
||||||
InputStream is = mXmppConnectionService.getContentResolver().openInputStream(uri);
|
is = mXmppConnectionService.getContentResolver().openInputStream(uri);
|
||||||
byte[] buffer = new byte[1024];
|
byte[] buffer = new byte[1024];
|
||||||
int length;
|
int length;
|
||||||
while ((length = is.read(buffer)) > 0) {
|
while ((length = is.read(buffer)) > 0) {
|
||||||
os.write(buffer, 0, length);
|
os.write(buffer, 0, length);
|
||||||
}
|
}
|
||||||
os.flush();
|
os.flush();
|
||||||
os.close();
|
} catch(FileNotFoundException e) {
|
||||||
is.close();
|
|
||||||
Log.d(Config.LOGTAG, "output file name " + mXmppConnectionService.getFileBackend().getFile(message));
|
|
||||||
return file;
|
|
||||||
} catch (FileNotFoundException e) {
|
|
||||||
throw new FileCopyException(R.string.error_file_not_found);
|
throw new FileCopyException(R.string.error_file_not_found);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
throw new FileCopyException(R.string.error_io_exception);
|
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)
|
public DownloadableFile copyImageToPrivateStorage(Message message, Uri image)
|
||||||
|
@ -182,49 +184,48 @@ public class FileBackend {
|
||||||
|
|
||||||
private DownloadableFile copyImageToPrivateStorage(Message message,
|
private DownloadableFile copyImageToPrivateStorage(Message message,
|
||||||
Uri image, int sampleSize) throws FileCopyException {
|
Uri image, int sampleSize) throws FileCopyException {
|
||||||
|
DownloadableFile file = getFile(message);
|
||||||
|
file.getParentFile().mkdirs();
|
||||||
|
InputStream is = null;
|
||||||
|
OutputStream os = null;
|
||||||
try {
|
try {
|
||||||
InputStream is = mXmppConnectionService.getContentResolver()
|
|
||||||
.openInputStream(image);
|
|
||||||
DownloadableFile file = getFile(message);
|
|
||||||
file.getParentFile().mkdirs();
|
|
||||||
file.createNewFile();
|
file.createNewFile();
|
||||||
|
is = mXmppConnectionService.getContentResolver().openInputStream(image);
|
||||||
|
os = new FileOutputStream(file);
|
||||||
|
|
||||||
Bitmap originalBitmap;
|
Bitmap originalBitmap;
|
||||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||||
int inSampleSize = (int) Math.pow(2, sampleSize);
|
int inSampleSize = (int) Math.pow(2, sampleSize);
|
||||||
Log.d(Config.LOGTAG, "reading bitmap with sample size "
|
Log.d(Config.LOGTAG, "reading bitmap with sample size " + inSampleSize);
|
||||||
+ inSampleSize);
|
|
||||||
options.inSampleSize = inSampleSize;
|
options.inSampleSize = inSampleSize;
|
||||||
originalBitmap = BitmapFactory.decodeStream(is, null, options);
|
originalBitmap = BitmapFactory.decodeStream(is, null, options);
|
||||||
is.close();
|
is.close();
|
||||||
if (originalBitmap == null) {
|
if (originalBitmap == null) {
|
||||||
throw new FileCopyException(R.string.error_not_an_image_file);
|
throw new FileCopyException(R.string.error_not_an_image_file);
|
||||||
}
|
}
|
||||||
Bitmap scalledBitmap = resize(originalBitmap, IMAGE_SIZE);
|
Bitmap scaledBitmap = resize(originalBitmap, IMAGE_SIZE);
|
||||||
originalBitmap = null;
|
|
||||||
int rotation = getRotation(image);
|
int rotation = getRotation(image);
|
||||||
if (rotation > 0) {
|
if (rotation > 0) {
|
||||||
scalledBitmap = rotate(scalledBitmap, rotation);
|
scaledBitmap = rotate(scaledBitmap, rotation);
|
||||||
}
|
}
|
||||||
OutputStream os = new FileOutputStream(file);
|
|
||||||
boolean success = scalledBitmap.compress(
|
boolean success = scaledBitmap.compress(Bitmap.CompressFormat.WEBP, 75, os);
|
||||||
Bitmap.CompressFormat.WEBP, 75, os);
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
throw new FileCopyException(R.string.error_compressing_image);
|
throw new FileCopyException(R.string.error_compressing_image);
|
||||||
}
|
}
|
||||||
os.flush();
|
os.flush();
|
||||||
os.close();
|
|
||||||
long size = file.getSize();
|
long size = file.getSize();
|
||||||
int width = scalledBitmap.getWidth();
|
int width = scaledBitmap.getWidth();
|
||||||
int height = scalledBitmap.getHeight();
|
int height = scaledBitmap.getHeight();
|
||||||
message.setBody(Long.toString(size) + ',' + width + ',' + height);
|
message.setBody(Long.toString(size) + ',' + width + ',' + height);
|
||||||
return file;
|
return file;
|
||||||
} catch (FileNotFoundException e) {
|
} catch (FileNotFoundException e) {
|
||||||
throw new FileCopyException(R.string.error_file_not_found);
|
throw new FileCopyException(R.string.error_file_not_found);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
throw new FileCopyException(R.string.error_io_exception);
|
throw new FileCopyException(R.string.error_io_exception);
|
||||||
} catch (SecurityException e) {
|
} catch (SecurityException e) {
|
||||||
throw new FileCopyException(
|
throw new FileCopyException(R.string.error_security_exception_during_image_copy);
|
||||||
R.string.error_security_exception_during_image_copy);
|
|
||||||
} catch (OutOfMemoryError e) {
|
} catch (OutOfMemoryError e) {
|
||||||
++sampleSize;
|
++sampleSize;
|
||||||
if (sampleSize <= 3) {
|
if (sampleSize <= 3) {
|
||||||
|
@ -232,23 +233,24 @@ public class FileBackend {
|
||||||
} else {
|
} else {
|
||||||
throw new FileCopyException(R.string.error_out_of_memory);
|
throw new FileCopyException(R.string.error_out_of_memory);
|
||||||
}
|
}
|
||||||
|
} finally {
|
||||||
|
close(os);
|
||||||
|
close(is);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getRotation(Uri image) {
|
private int getRotation(Uri image) {
|
||||||
|
InputStream is = null;
|
||||||
try {
|
try {
|
||||||
InputStream is = mXmppConnectionService.getContentResolver()
|
is = mXmppConnectionService.getContentResolver().openInputStream(image);
|
||||||
.openInputStream(image);
|
|
||||||
return ExifHelper.getOrientation(is);
|
return ExifHelper.getOrientation(is);
|
||||||
} catch (FileNotFoundException e) {
|
} catch (FileNotFoundException e) {
|
||||||
return 0;
|
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)
|
public Bitmap getThumbnail(Message message, int size, boolean cacheOnly)
|
||||||
throws FileNotFoundException {
|
throws FileNotFoundException {
|
||||||
Bitmap thumbnail = mXmppConnectionService.getBitmapCache().get(
|
Bitmap thumbnail = mXmppConnectionService.getBitmapCache().get(
|
||||||
|
@ -257,8 +259,7 @@ public class FileBackend {
|
||||||
File file = getFile(message);
|
File file = getFile(message);
|
||||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||||
options.inSampleSize = calcSampleSize(file, size);
|
options.inSampleSize = calcSampleSize(file, size);
|
||||||
Bitmap fullsize = BitmapFactory.decodeFile(file.getAbsolutePath(),
|
Bitmap fullsize = BitmapFactory.decodeFile(file.getAbsolutePath(),options);
|
||||||
options);
|
|
||||||
if (fullsize == null) {
|
if (fullsize == null) {
|
||||||
throw new FileNotFoundException();
|
throw new FileNotFoundException();
|
||||||
}
|
}
|
||||||
|
@ -271,13 +272,11 @@ public class FileBackend {
|
||||||
|
|
||||||
public Uri getTakePhotoUri() {
|
public Uri getTakePhotoUri() {
|
||||||
StringBuilder pathBuilder = new StringBuilder();
|
StringBuilder pathBuilder = new StringBuilder();
|
||||||
pathBuilder.append(Environment
|
pathBuilder.append(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM));
|
||||||
.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM));
|
|
||||||
pathBuilder.append('/');
|
pathBuilder.append('/');
|
||||||
pathBuilder.append("Camera");
|
pathBuilder.append("Camera");
|
||||||
pathBuilder.append('/');
|
pathBuilder.append('/');
|
||||||
pathBuilder.append("IMG_" + this.imageDateFormat.format(new Date())
|
pathBuilder.append("IMG_" + this.imageDateFormat.format(new Date()) + ".jpg");
|
||||||
+ ".jpg");
|
|
||||||
Uri uri = Uri.parse("file://" + pathBuilder.toString());
|
Uri uri = Uri.parse("file://" + pathBuilder.toString());
|
||||||
File file = new File(uri.toString());
|
File file = new File(uri.toString());
|
||||||
file.getParentFile().mkdirs();
|
file.getParentFile().mkdirs();
|
||||||
|
@ -325,13 +324,13 @@ public class FileBackend {
|
||||||
String filename = getAvatarPath(avatar.getFilename());
|
String filename = getAvatarPath(avatar.getFilename());
|
||||||
file = new File(filename + ".tmp");
|
file = new File(filename + ".tmp");
|
||||||
file.getParentFile().mkdirs();
|
file.getParentFile().mkdirs();
|
||||||
|
OutputStream os = null;
|
||||||
try {
|
try {
|
||||||
file.createNewFile();
|
file.createNewFile();
|
||||||
FileOutputStream mFileOutputStream = new FileOutputStream(file);
|
os = new FileOutputStream(file);
|
||||||
MessageDigest digest = MessageDigest.getInstance("SHA-1");
|
MessageDigest digest = MessageDigest.getInstance("SHA-1");
|
||||||
digest.reset();
|
digest.reset();
|
||||||
DigestOutputStream mDigestOutputStream = new DigestOutputStream(
|
DigestOutputStream mDigestOutputStream = new DigestOutputStream(os, digest);
|
||||||
mFileOutputStream, digest);
|
|
||||||
mDigestOutputStream.write(avatar.getImageAsBytes());
|
mDigestOutputStream.write(avatar.getImageAsBytes());
|
||||||
mDigestOutputStream.flush();
|
mDigestOutputStream.flush();
|
||||||
mDigestOutputStream.close();
|
mDigestOutputStream.close();
|
||||||
|
@ -349,6 +348,8 @@ public class FileBackend {
|
||||||
return false;
|
return false;
|
||||||
} catch (NoSuchAlgorithmException e) {
|
} catch (NoSuchAlgorithmException e) {
|
||||||
return false;
|
return false;
|
||||||
|
} finally {
|
||||||
|
close(os);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
avatar.size = file.length();
|
avatar.size = file.length();
|
||||||
|
@ -356,8 +357,7 @@ public class FileBackend {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getAvatarPath(String avatar) {
|
public String getAvatarPath(String avatar) {
|
||||||
return mXmppConnectionService.getFilesDir().getAbsolutePath()
|
return mXmppConnectionService.getFilesDir().getAbsolutePath()+ "/avatars/" + avatar;
|
||||||
+ "/avatars/" + avatar;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Uri getAvatarUri(String avatar) {
|
public Uri getAvatarUri(String avatar) {
|
||||||
|
@ -368,10 +368,11 @@ public class FileBackend {
|
||||||
if (image == null) {
|
if (image == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
InputStream is = null;
|
||||||
try {
|
try {
|
||||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||||
options.inSampleSize = calcSampleSize(image, size);
|
options.inSampleSize = calcSampleSize(image, size);
|
||||||
InputStream is = mXmppConnectionService.getContentResolver().openInputStream(image);
|
is = mXmppConnectionService.getContentResolver().openInputStream(image);
|
||||||
Bitmap input = BitmapFactory.decodeStream(is, null, options);
|
Bitmap input = BitmapFactory.decodeStream(is, null, options);
|
||||||
if (input == null) {
|
if (input == null) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -384,6 +385,8 @@ public class FileBackend {
|
||||||
}
|
}
|
||||||
} catch (FileNotFoundException e) {
|
} catch (FileNotFoundException e) {
|
||||||
return null;
|
return null;
|
||||||
|
} finally {
|
||||||
|
close(is);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -391,12 +394,15 @@ public class FileBackend {
|
||||||
if (image == null) {
|
if (image == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
InputStream is = null;
|
||||||
try {
|
try {
|
||||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||||
options.inSampleSize = calcSampleSize(image,Math.max(newHeight, newWidth));
|
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);
|
Bitmap source = BitmapFactory.decodeStream(is, null, options);
|
||||||
|
if (source == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
int sourceWidth = source.getWidth();
|
int sourceWidth = source.getWidth();
|
||||||
int sourceHeight = source.getHeight();
|
int sourceHeight = source.getHeight();
|
||||||
float xScale = (float) newWidth / sourceWidth;
|
float xScale = (float) newWidth / sourceWidth;
|
||||||
|
@ -408,14 +414,15 @@ public class FileBackend {
|
||||||
float top = (newHeight - scaledHeight) / 2;
|
float top = (newHeight - scaledHeight) / 2;
|
||||||
|
|
||||||
RectF targetRect = new RectF(left, top, left + scaledWidth, top + scaledHeight);
|
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 canvas = new Canvas(dest);
|
||||||
canvas.drawBitmap(source, null, targetRect, null);
|
canvas.drawBitmap(source, null, targetRect, null);
|
||||||
return dest;
|
return dest;
|
||||||
} catch (FileNotFoundException e) {
|
} catch (FileNotFoundException e) {
|
||||||
return null;
|
return null;
|
||||||
|
} finally {
|
||||||
|
close(is);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Bitmap cropCenterSquare(Bitmap input, int size) {
|
public Bitmap cropCenterSquare(Bitmap input, int size) {
|
||||||
|
@ -430,7 +437,7 @@ public class FileBackend {
|
||||||
float top = (size - outHeight) / 2;
|
float top = (size - outHeight) / 2;
|
||||||
RectF target = new RectF(left, top, left + outWidth, top + outHeight);
|
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 canvas = new Canvas(output);
|
||||||
canvas.drawBitmap(input, null, target, null);
|
canvas.drawBitmap(input, null, target, null);
|
||||||
return output;
|
return output;
|
||||||
|
@ -522,4 +529,13 @@ public class FileBackend {
|
||||||
public boolean isFileAvailable(Message message) {
|
public boolean isFileAvailable(Message message) {
|
||||||
return getFile(message).exists();
|
return getFile(message).exists();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void close(Closeable stream) {
|
||||||
|
if (stream != null) {
|
||||||
|
try {
|
||||||
|
stream.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -454,7 +454,7 @@ public class NotificationService {
|
||||||
// nick (matched in case-insensitive manner), followed by optional
|
// nick (matched in case-insensitive manner), followed by optional
|
||||||
// punctuation (for example "bob: i disagree" or "how are you alice?"),
|
// punctuation (for example "bob: i disagree" or "how are you alice?"),
|
||||||
// followed by another word boundary.
|
// 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);
|
Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -493,7 +493,7 @@ public class NotificationService {
|
||||||
final int cancelIcon;
|
final int cancelIcon;
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||||
mBuilder.setCategory(Notification.CATEGORY_SERVICE);
|
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;
|
cancelIcon = R.drawable.ic_cancel_white_24dp;
|
||||||
} else {
|
} else {
|
||||||
mBuilder.setSmallIcon(R.drawable.ic_stat_communication_import_export);
|
mBuilder.setSmallIcon(R.drawable.ic_stat_communication_import_export);
|
||||||
|
@ -540,7 +540,7 @@ public class NotificationService {
|
||||||
mBuilder.setOngoing(true);
|
mBuilder.setOngoing(true);
|
||||||
//mBuilder.setLights(0xffffffff, 2000, 4000);
|
//mBuilder.setLights(0xffffffff, 2000, 4000);
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
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 {
|
} else {
|
||||||
mBuilder.setSmallIcon(R.drawable.ic_stat_alert_warning);
|
mBuilder.setSmallIcon(R.drawable.ic_stat_alert_warning);
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,6 +36,7 @@ import org.openintents.openpgp.util.OpenPgpServiceConnection;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
@ -174,13 +175,22 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
|
||||||
public void onContactStatusChanged(Contact contact, boolean online) {
|
public void onContactStatusChanged(Contact contact, boolean online) {
|
||||||
Conversation conversation = find(getConversations(), contact);
|
Conversation conversation = find(getConversations(), contact);
|
||||||
if (conversation != null) {
|
if (conversation != null) {
|
||||||
if (online && contact.getPresences().size() > 1) {
|
if (online) {
|
||||||
conversation.endOtrIfNeeded();
|
conversation.endOtrIfNeeded();
|
||||||
|
if (contact.getPresences().size() == 1) {
|
||||||
|
sendUnsentMessages(conversation);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
conversation.resetOtrSession();
|
if (contact.getPresences().size() >= 1) {
|
||||||
}
|
if (conversation.hasValidOtrSession()) {
|
||||||
if (online && (contact.getPresences().size() == 1)) {
|
String otrResource = conversation.getOtrSession().getSessionID().getUserID();
|
||||||
sendUnsentMessages(conversation);
|
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());
|
ExceptionHelper.init(getApplicationContext());
|
||||||
PRNGFixes.apply();
|
PRNGFixes.apply();
|
||||||
this.mRandom = new SecureRandom();
|
this.mRandom = new SecureRandom();
|
||||||
this.mMemorizingTrustManager = new MemorizingTrustManager(
|
updateMemorizingTrustmanager();
|
||||||
getApplicationContext());
|
|
||||||
|
|
||||||
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
|
final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
|
||||||
final int cacheSize = maxMemory / 8;
|
final int cacheSize = maxMemory / 8;
|
||||||
this.mBitmapCache = new LruCache<String, Bitmap>(cacheSize) {
|
this.mBitmapCache = new LruCache<String, Bitmap>(cacheSize) {
|
||||||
|
@ -1129,6 +1137,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
|
||||||
}
|
}
|
||||||
|
|
||||||
public void archiveConversation(Conversation conversation) {
|
public void archiveConversation(Conversation conversation) {
|
||||||
|
getNotificationService().clear(conversation);
|
||||||
conversation.setStatus(Conversation.STATUS_ARCHIVED);
|
conversation.setStatus(Conversation.STATUS_ARCHIVED);
|
||||||
conversation.setNextEncryption(-1);
|
conversation.setNextEncryption(-1);
|
||||||
synchronized (this.conversations) {
|
synchronized (this.conversations) {
|
||||||
|
@ -1538,6 +1547,9 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
|
||||||
for (Jid invite : jids) {
|
for (Jid invite : jids) {
|
||||||
invite(conversation, invite);
|
invite(conversation, invite);
|
||||||
}
|
}
|
||||||
|
if (account.countPresences() > 1) {
|
||||||
|
directInvite(conversation, account.getJid().toBareJid());
|
||||||
|
}
|
||||||
if (callback != null) {
|
if (callback != null) {
|
||||||
callback.success(conversation);
|
callback.success(conversation);
|
||||||
}
|
}
|
||||||
|
@ -1700,6 +1712,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
sendOfflinePresence(account);
|
||||||
}
|
}
|
||||||
account.getXmppConnection().disconnect(force);
|
account.getXmppConnection().disconnect(force);
|
||||||
}
|
}
|
||||||
|
@ -2022,6 +2035,11 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
|
||||||
sendMessagePacket(conversation.getAccount(), packet);
|
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) {
|
public void resetSendingToWaiting(Account account) {
|
||||||
for (Conversation conversation : getConversations()) {
|
for (Conversation conversation : getConversations()) {
|
||||||
if (conversation.getAccount() == account) {
|
if (conversation.getAccount() == account) {
|
||||||
|
@ -2185,6 +2203,21 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
|
||||||
return this.mMemorizingTrustManager;
|
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() {
|
public PowerManager getPowerManager() {
|
||||||
return this.pm;
|
return this.pm;
|
||||||
}
|
}
|
||||||
|
@ -2260,6 +2293,10 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa
|
||||||
sendPresencePacket(account, mPresenceGenerator.sendPresence(account));
|
sendPresencePacket(account, mPresenceGenerator.sendPresence(account));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void sendOfflinePresence(final Account account) {
|
||||||
|
sendPresencePacket(account, mPresenceGenerator.sendOfflinePresence(account));
|
||||||
|
}
|
||||||
|
|
||||||
public MessageGenerator getMessageGenerator() {
|
public MessageGenerator getMessageGenerator() {
|
||||||
return this.mMessageGenerator;
|
return this.mMessageGenerator;
|
||||||
}
|
}
|
||||||
|
|
|
@ -237,6 +237,9 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers
|
||||||
MenuItem menuItemDeleteBookmark = menu.findItem(R.id.action_delete_bookmark);
|
MenuItem menuItemDeleteBookmark = menu.findItem(R.id.action_delete_bookmark);
|
||||||
MenuItem menuItemAdvancedMode = menu.findItem(R.id.action_advanced_mode);
|
MenuItem menuItemAdvancedMode = menu.findItem(R.id.action_advanced_mode);
|
||||||
menuItemAdvancedMode.setChecked(mAdvancedMode);
|
menuItemAdvancedMode.setChecked(mAdvancedMode);
|
||||||
|
if (mConversation == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
Account account = mConversation.getAccount();
|
Account account = mConversation.getAccount();
|
||||||
if (account.hasBookmarkFor(mConversation.getJid().toBareJid())) {
|
if (account.hasBookmarkFor(mConversation.getJid().toBareJid())) {
|
||||||
menuItemSaveBookmark.setVisible(false);
|
menuItemSaveBookmark.setVisible(false);
|
||||||
|
|
|
@ -256,16 +256,19 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd
|
||||||
MenuItem unblock = menu.findItem(R.id.action_unblock);
|
MenuItem unblock = menu.findItem(R.id.action_unblock);
|
||||||
MenuItem edit = menu.findItem(R.id.action_edit_contact);
|
MenuItem edit = menu.findItem(R.id.action_edit_contact);
|
||||||
MenuItem delete = menu.findItem(R.id.action_delete_contact);
|
MenuItem delete = menu.findItem(R.id.action_delete_contact);
|
||||||
|
if (contact == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
final XmppConnection connection = contact.getAccount().getXmppConnection();
|
final XmppConnection connection = contact.getAccount().getXmppConnection();
|
||||||
if (connection != null && connection.getFeatures().blocking()) {
|
if (connection != null && connection.getFeatures().blocking()) {
|
||||||
if (this.contact.isBlocked()) {
|
if (this.contact.isBlocked()) {
|
||||||
menu.findItem(R.id.action_block).setVisible(false);
|
block.setVisible(false);
|
||||||
} else {
|
} else {
|
||||||
menu.findItem(R.id.action_unblock).setVisible(false);
|
unblock.setVisible(false);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
menu.findItem(R.id.action_unblock).setVisible(false);
|
unblock.setVisible(false);
|
||||||
menu.findItem(R.id.action_block).setVisible(false);
|
block.setVisible(false);
|
||||||
}
|
}
|
||||||
if (!contact.showInRoster()) {
|
if (!contact.showInRoster()) {
|
||||||
edit.setVisible(false);
|
edit.setVisible(false);
|
||||||
|
@ -275,6 +278,7 @@ public class ContactDetailsActivity extends XmppActivity implements OnAccountUpd
|
||||||
}
|
}
|
||||||
|
|
||||||
private void populateView() {
|
private void populateView() {
|
||||||
|
invalidateOptionsMenu();
|
||||||
setTitle(contact.getDisplayName());
|
setTitle(contact.getDisplayName());
|
||||||
if (contact.showInRoster()) {
|
if (contact.showInRoster()) {
|
||||||
send.setVisibility(View.VISIBLE);
|
send.setVisibility(View.VISIBLE);
|
||||||
|
|
|
@ -5,6 +5,7 @@ import android.app.ActionBar;
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.app.FragmentTransaction;
|
import android.app.FragmentTransaction;
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
|
import android.content.ClipData;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.DialogInterface.OnClickListener;
|
import android.content.DialogInterface.OnClickListener;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
@ -22,14 +23,15 @@ import android.widget.AdapterView;
|
||||||
import android.widget.AdapterView.OnItemClickListener;
|
import android.widget.AdapterView.OnItemClickListener;
|
||||||
import android.widget.ArrayAdapter;
|
import android.widget.ArrayAdapter;
|
||||||
import android.widget.CheckBox;
|
import android.widget.CheckBox;
|
||||||
import android.widget.ListView;
|
|
||||||
import android.widget.PopupMenu;
|
import android.widget.PopupMenu;
|
||||||
import android.widget.PopupMenu.OnMenuItemClickListener;
|
import android.widget.PopupMenu.OnMenuItemClickListener;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import net.java.otr4j.session.SessionStatus;
|
import net.java.otr4j.session.SessionStatus;
|
||||||
|
import de.timroes.android.listview.EnhancedListView;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import eu.siacs.conversations.R;
|
import eu.siacs.conversations.R;
|
||||||
|
@ -69,15 +71,16 @@ public class ConversationActivity extends XmppActivity
|
||||||
|
|
||||||
private String mOpenConverstaion = null;
|
private String mOpenConverstaion = null;
|
||||||
private boolean mPanelOpen = true;
|
private boolean mPanelOpen = true;
|
||||||
private Uri mPendingImageUri = null;
|
final private List<Uri> mPendingImageUris = new ArrayList<>();
|
||||||
private Uri mPendingFileUri = null;
|
final private List<Uri> mPendingFileUris = new ArrayList<>();
|
||||||
private Uri mPendingGeoUri = null;
|
private Uri mPendingGeoUri = null;
|
||||||
|
|
||||||
private View mContentView;
|
private View mContentView;
|
||||||
|
|
||||||
private List<Conversation> conversationList = new ArrayList<>();
|
private List<Conversation> conversationList = new ArrayList<>();
|
||||||
|
private Conversation swipedConversation = null;
|
||||||
private Conversation mSelectedConversation = null;
|
private Conversation mSelectedConversation = null;
|
||||||
private ListView listView;
|
private EnhancedListView listView;
|
||||||
private ConversationFragment mConversationFragment;
|
private ConversationFragment mConversationFragment;
|
||||||
|
|
||||||
private ArrayAdapter<Conversation> listAdapter;
|
private ArrayAdapter<Conversation> listAdapter;
|
||||||
|
@ -140,13 +143,14 @@ public class ConversationActivity extends XmppActivity
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(final Bundle savedInstanceState) {
|
protected void onCreate(final Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
if (savedInstanceState != null) {mOpenConverstaion = savedInstanceState.getString(
|
if (savedInstanceState != null) {
|
||||||
STATE_OPEN_CONVERSATION, null);
|
mOpenConverstaion = savedInstanceState.getString(STATE_OPEN_CONVERSATION, null);
|
||||||
mPanelOpen = savedInstanceState.getBoolean(STATE_PANEL_OPEN, true);
|
mPanelOpen = savedInstanceState.getBoolean(STATE_PANEL_OPEN, true);
|
||||||
String pending = savedInstanceState.getString(STATE_PENDING_URI, null);
|
String pending = savedInstanceState.getString(STATE_PENDING_URI, null);
|
||||||
if (pending != null) {
|
if (pending != null) {
|
||||||
mPendingImageUri = Uri.parse(pending);
|
mPendingImageUris.clear();
|
||||||
}
|
mPendingImageUris.add(Uri.parse(pending));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setContentView(R.layout.fragment_conversations_overview);
|
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.replace(R.id.selected_conversation, this.mConversationFragment, "conversation");
|
||||||
transaction.commit();
|
transaction.commit();
|
||||||
|
|
||||||
listView = (ListView) findViewById(R.id.list);
|
listView = (EnhancedListView) findViewById(R.id.list);
|
||||||
this.listAdapter = new ConversationAdapter(this, conversationList);
|
this.listAdapter = new ConversationAdapter(this, conversationList);
|
||||||
listView.setAdapter(this.listAdapter);
|
listView.setAdapter(this.listAdapter);
|
||||||
|
|
||||||
|
@ -178,6 +182,73 @@ public class ConversationActivity extends XmppActivity
|
||||||
openConversation();
|
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);
|
mContentView = findViewById(R.id.content_view_spl);
|
||||||
if (mContentView == null) {
|
if (mContentView == null) {
|
||||||
mContentView = findViewById(R.id.content_view_ll);
|
mContentView = findViewById(R.id.content_view_ll);
|
||||||
|
@ -204,6 +275,7 @@ public class ConversationActivity extends XmppActivity
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPanelClosed(View arg0) {
|
public void onPanelClosed(View arg0) {
|
||||||
|
listView.discardUndo();
|
||||||
openConversation();
|
openConversation();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -303,7 +375,7 @@ public class ConversationActivity extends XmppActivity
|
||||||
if (this.getSelectedConversation().getLatestMessage()
|
if (this.getSelectedConversation().getLatestMessage()
|
||||||
.getEncryption() != Message.ENCRYPTION_NONE) {
|
.getEncryption() != Message.ENCRYPTION_NONE) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
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 {
|
} else {
|
||||||
menuSecure.setIcon(R.drawable.ic_action_secure);
|
menuSecure.setIcon(R.drawable.ic_action_secure);
|
||||||
}
|
}
|
||||||
|
@ -340,13 +412,18 @@ public class ConversationActivity extends XmppActivity
|
||||||
switch (attachmentChoice) {
|
switch (attachmentChoice) {
|
||||||
case ATTACHMENT_CHOICE_CHOOSE_IMAGE:
|
case ATTACHMENT_CHOICE_CHOOSE_IMAGE:
|
||||||
intent.setAction(Intent.ACTION_GET_CONTENT);
|
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/*");
|
intent.setType("image/*");
|
||||||
chooser = true;
|
chooser = true;
|
||||||
break;
|
break;
|
||||||
case ATTACHMENT_CHOICE_TAKE_PHOTO:
|
case ATTACHMENT_CHOICE_TAKE_PHOTO:
|
||||||
mPendingImageUri = xmppConnectionService.getFileBackend().getTakePhotoUri();
|
Uri uri = xmppConnectionService.getFileBackend().getTakePhotoUri();
|
||||||
intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
|
intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE);
|
||||||
intent.putExtra(MediaStore.EXTRA_OUTPUT, mPendingImageUri);
|
intent.putExtra(MediaStore.EXTRA_OUTPUT, uri);
|
||||||
|
mPendingImageUris.clear();
|
||||||
|
mPendingImageUris.add(uri);
|
||||||
break;
|
break;
|
||||||
case ATTACHMENT_CHOICE_CHOOSE_FILE:
|
case ATTACHMENT_CHOICE_CHOOSE_FILE:
|
||||||
chooser = true;
|
chooser = true;
|
||||||
|
@ -485,13 +562,21 @@ public class ConversationActivity extends XmppActivity
|
||||||
}
|
}
|
||||||
|
|
||||||
public void endConversation(Conversation conversation) {
|
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);
|
xmppConnectionService.archiveConversation(conversation);
|
||||||
if (conversationList.size() > 0) {
|
if (reinit) {
|
||||||
setSelectedConversation(conversationList.get(0));
|
if (conversationList.size() > 0) {
|
||||||
this.mConversationFragment.reInit(getSelectedConversation());
|
setSelectedConversation(conversationList.get(0));
|
||||||
} else {
|
this.mConversationFragment.reInit(getSelectedConversation());
|
||||||
setSelectedConversation(null);
|
} else {
|
||||||
|
setSelectedConversation(null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -744,6 +829,7 @@ public class ConversationActivity extends XmppActivity
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPause() {
|
public void onPause() {
|
||||||
|
listView.discardUndo();
|
||||||
super.onPause();
|
super.onPause();
|
||||||
this.mActivityPaused = true;
|
this.mActivityPaused = true;
|
||||||
if (this.xmppConnectionServiceBound) {
|
if (this.xmppConnectionServiceBound) {
|
||||||
|
@ -779,8 +865,8 @@ public class ConversationActivity extends XmppActivity
|
||||||
}
|
}
|
||||||
savedInstanceState.putBoolean(STATE_PANEL_OPEN,
|
savedInstanceState.putBoolean(STATE_PANEL_OPEN,
|
||||||
isConversationsOverviewVisable());
|
isConversationsOverviewVisable());
|
||||||
if (this.mPendingImageUri != null) {
|
if (this.mPendingImageUris.size() >= 1) {
|
||||||
savedInstanceState.putString(STATE_PENDING_URI, this.mPendingImageUri.toString());
|
savedInstanceState.putString(STATE_PENDING_URI, this.mPendingImageUris.get(0).toString());
|
||||||
}
|
}
|
||||||
super.onSaveInstanceState(savedInstanceState);
|
super.onSaveInstanceState(savedInstanceState);
|
||||||
}
|
}
|
||||||
|
@ -819,21 +905,23 @@ public class ConversationActivity extends XmppActivity
|
||||||
this.mConversationFragment.reInit(getSelectedConversation());
|
this.mConversationFragment.reInit(getSelectedConversation());
|
||||||
} else {
|
} else {
|
||||||
showConversationsOverview();
|
showConversationsOverview();
|
||||||
mPendingImageUri = null;
|
mPendingImageUris.clear();
|
||||||
mPendingFileUri = null;
|
mPendingFileUris.clear();
|
||||||
mPendingGeoUri = null;
|
mPendingGeoUri = null;
|
||||||
setSelectedConversation(conversationList.get(0));
|
setSelectedConversation(conversationList.get(0));
|
||||||
this.mConversationFragment.reInit(getSelectedConversation());
|
this.mConversationFragment.reInit(getSelectedConversation());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mPendingImageUri != null) {
|
for(Iterator<Uri> i = mPendingImageUris.iterator(); i.hasNext(); i.remove()) {
|
||||||
attachImageToConversation(getSelectedConversation(),mPendingImageUri);
|
attachImageToConversation(getSelectedConversation(),i.next());
|
||||||
mPendingImageUri = null;
|
}
|
||||||
} else if (mPendingFileUri != null) {
|
|
||||||
attachFileToConversation(getSelectedConversation(),mPendingFileUri);
|
for(Iterator<Uri> i = mPendingFileUris.iterator(); i.hasNext(); i.remove()) {
|
||||||
mPendingFileUri = null;
|
attachFileToConversation(getSelectedConversation(),i.next());
|
||||||
} else if (mPendingGeoUri != null) {
|
}
|
||||||
attachLocationToConversation(getSelectedConversation(),mPendingGeoUri);
|
|
||||||
|
if (mPendingGeoUri != null) {
|
||||||
|
attachLocationToConversation(getSelectedConversation(), mPendingGeoUri);
|
||||||
mPendingGeoUri = null;
|
mPendingGeoUri = null;
|
||||||
}
|
}
|
||||||
ExceptionHelper.checkForCrash(this, this.xmppConnectionService);
|
ExceptionHelper.checkForCrash(this, this.xmppConnectionService);
|
||||||
|
@ -841,10 +929,10 @@ public class ConversationActivity extends XmppActivity
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleViewConversationIntent(final Intent intent) {
|
private void handleViewConversationIntent(final Intent intent) {
|
||||||
final String uuid = (String) intent.getExtras().get(CONVERSATION);
|
final String uuid = intent.getStringExtra(CONVERSATION);
|
||||||
final String downloadUuid = (String) intent.getExtras().get(MESSAGE);
|
final String downloadUuid = intent.getStringExtra(MESSAGE);
|
||||||
final String text = intent.getExtras().getString(TEXT, "");
|
final String text = intent.getStringExtra(TEXT);
|
||||||
final String nick = intent.getExtras().getString(NICK, null);
|
final String nick = intent.getStringExtra(NICK);
|
||||||
if (selectConversationByUuid(uuid)) {
|
if (selectConversationByUuid(uuid)) {
|
||||||
this.mConversationFragment.reInit(getSelectedConversation());
|
this.mConversationFragment.reInit(getSelectedConversation());
|
||||||
if (nick != null) {
|
if (nick != null) {
|
||||||
|
@ -885,6 +973,21 @@ public class ConversationActivity extends XmppActivity
|
||||||
xmppConnectionService.getNotificationService().setOpenConversation(null);
|
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
|
@Override
|
||||||
protected void onActivityResult(int requestCode, int resultCode,
|
protected void onActivityResult(int requestCode, int resultCode,
|
||||||
final Intent data) {
|
final Intent data) {
|
||||||
|
@ -894,25 +997,34 @@ public class ConversationActivity extends XmppActivity
|
||||||
mConversationFragment.hideSnackbar();
|
mConversationFragment.hideSnackbar();
|
||||||
mConversationFragment.updateMessages();
|
mConversationFragment.updateMessages();
|
||||||
} else if (requestCode == ATTACHMENT_CHOICE_CHOOSE_IMAGE) {
|
} else if (requestCode == ATTACHMENT_CHOICE_CHOOSE_IMAGE) {
|
||||||
mPendingImageUri = data.getData();
|
mPendingImageUris.clear();
|
||||||
|
mPendingImageUris.addAll(extractUriFromIntent(data));
|
||||||
if (xmppConnectionServiceBound) {
|
if (xmppConnectionServiceBound) {
|
||||||
attachImageToConversation(getSelectedConversation(),mPendingImageUri);
|
for(Iterator<Uri> i = mPendingImageUris.iterator(); i.hasNext(); i.remove()) {
|
||||||
mPendingImageUri = null;
|
attachImageToConversation(getSelectedConversation(),i.next());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (requestCode == ATTACHMENT_CHOICE_CHOOSE_FILE || requestCode == ATTACHMENT_CHOICE_RECORD_VOICE) {
|
} else if (requestCode == ATTACHMENT_CHOICE_CHOOSE_FILE || requestCode == ATTACHMENT_CHOICE_RECORD_VOICE) {
|
||||||
mPendingFileUri = data.getData();
|
mPendingFileUris.clear();
|
||||||
|
mPendingFileUris.addAll(extractUriFromIntent(data));
|
||||||
if (xmppConnectionServiceBound) {
|
if (xmppConnectionServiceBound) {
|
||||||
attachFileToConversation(getSelectedConversation(),mPendingFileUri);
|
for(Iterator<Uri> i = mPendingImageUris.iterator(); i.hasNext(); i.remove()) {
|
||||||
mPendingFileUri = null;
|
attachFileToConversation(getSelectedConversation(), i.next());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else if (requestCode == ATTACHMENT_CHOICE_TAKE_PHOTO && mPendingImageUri != null) {
|
} else if (requestCode == ATTACHMENT_CHOICE_TAKE_PHOTO) {
|
||||||
if (xmppConnectionServiceBound) {
|
if (mPendingImageUris.size() == 1) {
|
||||||
attachImageToConversation(getSelectedConversation(),mPendingImageUri);
|
Uri uri = mPendingImageUris.get(0);
|
||||||
mPendingImageUri = null;
|
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) {
|
} else if (requestCode == ATTACHMENT_CHOICE_LOCATION) {
|
||||||
double latitude = data.getDoubleExtra("latitude",0);
|
double latitude = data.getDoubleExtra("latitude",0);
|
||||||
double longitude = data.getDoubleExtra("longitude",0);
|
double longitude = data.getDoubleExtra("longitude",0);
|
||||||
|
@ -923,9 +1035,8 @@ public class ConversationActivity extends XmppActivity
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (requestCode == ATTACHMENT_CHOICE_TAKE_PHOTO) {
|
mPendingImageUris.clear();
|
||||||
mPendingImageUri = null;
|
mPendingFileUris.clear();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1013,6 +1124,13 @@ public class ConversationActivity extends XmppActivity
|
||||||
public void updateConversationList() {
|
public void updateConversationList() {
|
||||||
xmppConnectionService
|
xmppConnectionService
|
||||||
.populateWithOrderedConversations(conversationList);
|
.populateWithOrderedConversations(conversationList);
|
||||||
|
if (swipedConversation != null) {
|
||||||
|
if (swipedConversation.isRead()) {
|
||||||
|
conversationList.remove(swipedConversation);
|
||||||
|
} else {
|
||||||
|
listView.discardUndo();
|
||||||
|
}
|
||||||
|
}
|
||||||
listAdapter.notifyDataSetChanged();
|
listAdapter.notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,6 @@ import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.IntentSender;
|
import android.content.IntentSender;
|
||||||
import android.content.IntentSender.SendIntentException;
|
import android.content.IntentSender.SendIntentException;
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.text.InputType;
|
import android.text.InputType;
|
||||||
import android.view.ContextMenu;
|
import android.view.ContextMenu;
|
||||||
|
@ -268,7 +267,6 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
|
||||||
if (conversation.getNextCounterpart() != null) {
|
if (conversation.getNextCounterpart() != null) {
|
||||||
message.setCounterpart(conversation.getNextCounterpart());
|
message.setCounterpart(conversation.getNextCounterpart());
|
||||||
message.setType(Message.TYPE_PRIVATE);
|
message.setType(Message.TYPE_PRIVATE);
|
||||||
conversation.setNextCounterpart(null);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (conversation.getNextEncryption(activity.forceEncryption()) == Message.ENCRYPTION_OTR) {
|
if (conversation.getNextEncryption(activity.forceEncryption()) == Message.ENCRYPTION_OTR) {
|
||||||
|
@ -316,8 +314,8 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(final LayoutInflater inflater,
|
public View onCreateView(final LayoutInflater inflater,
|
||||||
ViewGroup container, Bundle savedInstanceState) {
|
ViewGroup container, Bundle savedInstanceState) {
|
||||||
final View view = inflater.inflate(R.layout.fragment_conversation,
|
final View view = inflater.inflate(R.layout.fragment_conversation,container, false);
|
||||||
container, false);
|
view.setOnClickListener(null);
|
||||||
mEditMessage = (EditMessage) view.findViewById(R.id.textinput);
|
mEditMessage = (EditMessage) view.findViewById(R.id.textinput);
|
||||||
setupIme();
|
setupIme();
|
||||||
mEditMessage.setOnClickListener(new OnClickListener() {
|
mEditMessage.setOnClickListener(new OnClickListener() {
|
||||||
|
@ -720,21 +718,6 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
|
||||||
final ConversationActivity activity = (ConversationActivity) getActivity();
|
final ConversationActivity activity = (ConversationActivity) getActivity();
|
||||||
if (this.conversation != null) {
|
if (this.conversation != null) {
|
||||||
updateSnackBar(this.conversation);
|
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);
|
conversation.populateWithMessages(ConversationFragment.this.messageList);
|
||||||
for (final Message message : this.messageList) {
|
for (final Message message : this.messageList) {
|
||||||
if (message.getEncryption() == Message.ENCRYPTION_PGP
|
if (message.getEncryption() == Message.ENCRYPTION_PGP
|
||||||
|
@ -781,6 +764,7 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
|
||||||
} catch (final NoSuchElementException ignored) {
|
} catch (final NoSuchElementException ignored) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
askForPassphraseIntent = null;
|
||||||
activity.xmppConnectionService.updateMessage(message);
|
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,
|
protected void showSnackbar(final int message, final int action,
|
||||||
final OnClickListener clickListener) {
|
final OnClickListener clickListener) {
|
||||||
snackbar.setVisibility(View.VISIBLE);
|
snackbar.setVisibility(View.VISIBLE);
|
||||||
|
@ -1020,6 +1000,9 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa
|
||||||
}
|
}
|
||||||
|
|
||||||
public void appendText(String text) {
|
public void appendText(String text) {
|
||||||
|
if (text == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
String previous = this.mEditMessage.getText().toString();
|
String previous = this.mEditMessage.getText().toString();
|
||||||
if (previous.length() != 0 && !previous.endsWith(" ")) {
|
if (previous.length() != 0 && !previous.endsWith(" ")) {
|
||||||
text = " " + text;
|
text = " " + text;
|
||||||
|
|
|
@ -67,7 +67,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClick(final View v) {
|
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);
|
mAccount.setOption(Account.OPTION_DISABLED, false);
|
||||||
xmppConnectionService.updateAccount(mAccount);
|
xmppConnectionService.updateAccount(mAccount);
|
||||||
return;
|
return;
|
||||||
|
@ -237,7 +237,11 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void updateSaveButton() {
|
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.setEnabled(false);
|
||||||
this.mSaveButton.setTextColor(getSecondaryTextColor());
|
this.mSaveButton.setTextColor(getSecondaryTextColor());
|
||||||
this.mSaveButton.setText(R.string.account_status_connecting);
|
this.mSaveButton.setText(R.string.account_status_connecting);
|
||||||
|
@ -265,9 +269,9 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean accountInfoEdited() {
|
protected boolean accountInfoEdited() {
|
||||||
return (!this.mAccount.getJid().toBareJid().toString().equals(
|
return this.mAccount != null && (!this.mAccount.getJid().toBareJid().toString().equals(
|
||||||
this.mAccountJid.getText().toString()))
|
this.mAccountJid.getText().toString())
|
||||||
|| (!this.mAccount.getPassword().equals(
|
|| !this.mAccount.getPassword().equals(
|
||||||
this.mPassword.getText().toString()));
|
this.mPassword.getText().toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -464,7 +468,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate
|
||||||
} else {
|
} else {
|
||||||
this.mServerInfoSm.setText(R.string.server_info_unavailable);
|
this.mServerInfoSm.setText(R.string.server_info_unavailable);
|
||||||
}
|
}
|
||||||
if (features.pubsub()) {
|
if (features.pep()) {
|
||||||
this.mServerInfoPep.setText(R.string.server_info_available);
|
this.mServerInfoPep.setText(R.string.server_info_available);
|
||||||
} else {
|
} else {
|
||||||
this.mServerInfoPep.setText(R.string.server_info_unavailable);
|
this.mServerInfoPep.setText(R.string.server_info_unavailable);
|
||||||
|
|
|
@ -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) {
|
private void publishAvatar(Account account) {
|
||||||
Intent intent = new Intent(getApplicationContext(),
|
Intent intent = new Intent(getApplicationContext(),
|
||||||
PublishProfilePictureActivity.class);
|
PublishProfilePictureActivity.class);
|
||||||
|
|
|
@ -163,8 +163,7 @@ public class PublishProfilePictureActivity extends XmppActivity {
|
||||||
if (jid != null) {
|
if (jid != null) {
|
||||||
this.account = xmppConnectionService.findAccountByJid(jid);
|
this.account = xmppConnectionService.findAccountByJid(jid);
|
||||||
if (this.account.getXmppConnection() != null) {
|
if (this.account.getXmppConnection() != null) {
|
||||||
this.support = this.account.getXmppConnection()
|
this.support = this.account.getXmppConnection().getFeatures().pep();
|
||||||
.getFeatures().pubsub();
|
|
||||||
}
|
}
|
||||||
if (this.avatarUri == null) {
|
if (this.avatarUri == null) {
|
||||||
if (this.account.getAvatar() != null
|
if (this.account.getAvatar() != null
|
||||||
|
|
|
@ -1,17 +1,29 @@
|
||||||
package eu.siacs.conversations.ui;
|
package eu.siacs.conversations.ui;
|
||||||
|
|
||||||
|
import java.security.KeyStoreException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.Locale;
|
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;
|
||||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.preference.ListPreference;
|
import android.preference.ListPreference;
|
||||||
|
import android.preference.Preference;
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
public class SettingsActivity extends XmppActivity implements
|
public class SettingsActivity extends XmppActivity implements
|
||||||
OnSharedPreferenceChangeListener {
|
OnSharedPreferenceChangeListener {
|
||||||
|
@ -20,9 +32,12 @@ public class SettingsActivity extends XmppActivity implements
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
mSettingsFragment = new SettingsFragment();
|
FragmentManager fm = getFragmentManager();
|
||||||
getFragmentManager().beginTransaction()
|
mSettingsFragment = (SettingsFragment) fm.findFragmentById(android.R.id.content);
|
||||||
.replace(android.R.id.content, mSettingsFragment).commit();
|
if (mSettingsFragment == null || !mSettingsFragment.getClass().equals(SettingsFragment.class)) {
|
||||||
|
mSettingsFragment = new SettingsFragment();
|
||||||
|
fm.beginTransaction().replace(android.R.id.content, mSettingsFragment).commit();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -33,19 +48,78 @@ public class SettingsActivity extends XmppActivity implements
|
||||||
@Override
|
@Override
|
||||||
public void onStart() {
|
public void onStart() {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
PreferenceManager.getDefaultSharedPreferences(this)
|
PreferenceManager.getDefaultSharedPreferences(this).registerOnSharedPreferenceChangeListener(this);
|
||||||
.registerOnSharedPreferenceChangeListener(this);
|
ListPreference resources = (ListPreference) mSettingsFragment.findPreference("resource");
|
||||||
ListPreference resources = (ListPreference) mSettingsFragment
|
|
||||||
.findPreference("resource");
|
|
||||||
if (resources != null) {
|
if (resources != null) {
|
||||||
ArrayList<CharSequence> entries = new ArrayList<CharSequence>(
|
ArrayList<CharSequence> entries = new ArrayList<>(Arrays.asList(resources.getEntries()));
|
||||||
Arrays.asList(resources.getEntries()));
|
if (!entries.contains(Build.MODEL)) {
|
||||||
entries.add(0, Build.MODEL);
|
entries.add(0, Build.MODEL);
|
||||||
resources.setEntries(entries.toArray(new CharSequence[entries
|
resources.setEntries(entries.toArray(new CharSequence[entries.size()]));
|
||||||
.size()]));
|
resources.setEntryValues(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
|
@Override
|
||||||
|
@ -63,9 +137,14 @@ public class SettingsActivity extends XmppActivity implements
|
||||||
.toLowerCase(Locale.US);
|
.toLowerCase(Locale.US);
|
||||||
if (xmppConnectionServiceBound) {
|
if (xmppConnectionServiceBound) {
|
||||||
for (Account account : xmppConnectionService.getAccounts()) {
|
for (Account account : xmppConnectionService.getAccounts()) {
|
||||||
account.setResource(resource);
|
if (account.setResource(resource)) {
|
||||||
if (!account.isOptionSet(Account.OPTION_DISABLED)) {
|
if (!account.isOptionSet(Account.OPTION_DISABLED)) {
|
||||||
xmppConnectionService.reconnectAccountInBackground(account);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,6 +18,7 @@ import java.net.URLConnection;
|
||||||
import java.net.URLDecoder;
|
import java.net.URLDecoder;
|
||||||
import java.nio.charset.UnsupportedCharsetException;
|
import java.nio.charset.UnsupportedCharsetException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import eu.siacs.conversations.Config;
|
import eu.siacs.conversations.Config;
|
||||||
|
@ -32,7 +33,7 @@ import eu.siacs.conversations.xmpp.jid.Jid;
|
||||||
public class ShareWithActivity extends XmppActivity {
|
public class ShareWithActivity extends XmppActivity {
|
||||||
|
|
||||||
private class Share {
|
private class Share {
|
||||||
public Uri uri;
|
public List<Uri> uris = new ArrayList<>();
|
||||||
public boolean image;
|
public boolean image;
|
||||||
public String account;
|
public String account;
|
||||||
public String contact;
|
public String contact;
|
||||||
|
@ -104,7 +105,7 @@ public class ShareWithActivity extends XmppActivity {
|
||||||
int position, long arg3) {
|
int position, long arg3) {
|
||||||
Conversation conversation = mConversations.get(position);
|
Conversation conversation = mConversations.get(position);
|
||||||
if (conversation.getMode() == Conversation.MODE_SINGLE
|
if (conversation.getMode() == Conversation.MODE_SINGLE
|
||||||
|| share.uri == null) {
|
|| share.uris.size() == 0) {
|
||||||
share(mConversations.get(position));
|
share(mConversations.get(position));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -133,18 +134,32 @@ public class ShareWithActivity extends XmppActivity {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStart() {
|
public void onStart() {
|
||||||
final String type = getIntent().getType();
|
super.onStart();
|
||||||
final Uri uri = getIntent().getParcelableExtra(Intent.EXTRA_STREAM);
|
Intent intent = getIntent();
|
||||||
if (type != null && uri != null && !type.equalsIgnoreCase("text/plain")) {
|
if (intent == null) {
|
||||||
this.share.uri = uri;
|
return;
|
||||||
this.share.image = type.startsWith("image/") || isImage(uri);
|
}
|
||||||
} else {
|
final String type = intent.getType();
|
||||||
this.share.text = getIntent().getStringExtra(Intent.EXTRA_TEXT);
|
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) {
|
if (xmppConnectionServiceBound) {
|
||||||
xmppConnectionService.populateWithOrderedConversations(mConversations, this.share.uri == null);
|
xmppConnectionService.populateWithOrderedConversations(mConversations, this.share.image);
|
||||||
}
|
}
|
||||||
super.onStart();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean isImage(Uri uri) {
|
protected boolean isImage(Uri uri) {
|
||||||
|
@ -164,7 +179,7 @@ public class ShareWithActivity extends XmppActivity {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
xmppConnectionService.populateWithOrderedConversations(mConversations,
|
xmppConnectionService.populateWithOrderedConversations(mConversations,
|
||||||
this.share != null && this.share.uri == null);
|
this.share != null && this.share.uris.size() == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void share() {
|
private void share() {
|
||||||
|
@ -188,7 +203,7 @@ public class ShareWithActivity extends XmppActivity {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void share(final Conversation conversation) {
|
private void share(final Conversation conversation) {
|
||||||
if (share.uri != null) {
|
if (share.uris.size() != 0) {
|
||||||
selectPresence(conversation, new OnPresenceSelected() {
|
selectPresence(conversation, new OnPresenceSelected() {
|
||||||
@Override
|
@Override
|
||||||
public void onPresenceSelected() {
|
public void onPresenceSelected() {
|
||||||
|
@ -196,22 +211,23 @@ public class ShareWithActivity extends XmppActivity {
|
||||||
Toast.makeText(getApplicationContext(),
|
Toast.makeText(getApplicationContext(),
|
||||||
getText(R.string.preparing_image),
|
getText(R.string.preparing_image),
|
||||||
Toast.LENGTH_LONG).show();
|
Toast.LENGTH_LONG).show();
|
||||||
ShareWithActivity.this.xmppConnectionService
|
for (Iterator<Uri> i = share.uris.iterator(); i.hasNext(); i.remove()) {
|
||||||
.attachImageToConversation(conversation, share.uri,
|
ShareWithActivity.this.xmppConnectionService
|
||||||
attachFileCallback);
|
.attachImageToConversation(conversation, i.next(),
|
||||||
|
attachFileCallback);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(getApplicationContext(),
|
Toast.makeText(getApplicationContext(),
|
||||||
getText(R.string.preparing_file),
|
getText(R.string.preparing_file),
|
||||||
Toast.LENGTH_LONG).show();
|
Toast.LENGTH_LONG).show();
|
||||||
ShareWithActivity.this.xmppConnectionService
|
ShareWithActivity.this.xmppConnectionService
|
||||||
.attachFileToConversation(conversation, share.uri,
|
.attachFileToConversation(conversation, share.uris.get(0),
|
||||||
attachFileCallback);
|
attachFileCallback);
|
||||||
}
|
}
|
||||||
switchToConversation(conversation, null, true);
|
switchToConversation(conversation, null, true);
|
||||||
finish();
|
finish();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
switchToConversation(conversation, this.share.text, true);
|
switchToConversation(conversation, this.share.text, true);
|
||||||
finish();
|
finish();
|
||||||
|
|
|
@ -90,6 +90,7 @@ public abstract class XmppActivity extends Activity {
|
||||||
|
|
||||||
protected int mPrimaryTextColor;
|
protected int mPrimaryTextColor;
|
||||||
protected int mSecondaryTextColor;
|
protected int mSecondaryTextColor;
|
||||||
|
protected int mPrimaryBackgroundColor;
|
||||||
protected int mSecondaryBackgroundColor;
|
protected int mSecondaryBackgroundColor;
|
||||||
protected int mColorRed;
|
protected int mColorRed;
|
||||||
protected int mColorOrange;
|
protected int mColorOrange;
|
||||||
|
@ -331,6 +332,7 @@ public abstract class XmppActivity extends Activity {
|
||||||
mColorOrange = getResources().getColor(R.color.orange);
|
mColorOrange = getResources().getColor(R.color.orange);
|
||||||
mColorGreen = getResources().getColor(R.color.green);
|
mColorGreen = getResources().getColor(R.color.green);
|
||||||
mPrimaryColor = getResources().getColor(R.color.primary);
|
mPrimaryColor = getResources().getColor(R.color.primary);
|
||||||
|
mPrimaryBackgroundColor = getResources().getColor(R.color.primarybackground);
|
||||||
mSecondaryBackgroundColor = getResources().getColor(R.color.secondarybackground);
|
mSecondaryBackgroundColor = getResources().getColor(R.color.secondarybackground);
|
||||||
this.mTheme = findTheme();
|
this.mTheme = findTheme();
|
||||||
setTheme(this.mTheme);
|
setTheme(this.mTheme);
|
||||||
|
@ -741,6 +743,10 @@ public abstract class XmppActivity extends Activity {
|
||||||
return this.mColorGreen;
|
return this.mColorGreen;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int getPrimaryBackgroundColor() {
|
||||||
|
return this.mPrimaryBackgroundColor;
|
||||||
|
}
|
||||||
|
|
||||||
public int getSecondaryBackgroundColor() {
|
public int getSecondaryBackgroundColor() {
|
||||||
return this.mSecondaryBackgroundColor;
|
return this.mSecondaryBackgroundColor;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,13 +5,16 @@ import java.util.List;
|
||||||
import eu.siacs.conversations.R;
|
import eu.siacs.conversations.R;
|
||||||
import eu.siacs.conversations.entities.Account;
|
import eu.siacs.conversations.entities.Account;
|
||||||
import eu.siacs.conversations.ui.XmppActivity;
|
import eu.siacs.conversations.ui.XmppActivity;
|
||||||
|
import eu.siacs.conversations.ui.ManageAccountActivity;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.ArrayAdapter;
|
import android.widget.ArrayAdapter;
|
||||||
|
import android.widget.CompoundButton;
|
||||||
import android.widget.ImageView;
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
import android.widget.Switch;
|
||||||
|
|
||||||
public class AccountAdapter extends ArrayAdapter<Account> {
|
public class AccountAdapter extends ArrayAdapter<Account> {
|
||||||
|
|
||||||
|
@ -24,7 +27,7 @@ public class AccountAdapter extends ArrayAdapter<Account> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View getView(int position, View view, ViewGroup parent) {
|
public View getView(int position, View view, ViewGroup parent) {
|
||||||
Account account = getItem(position);
|
final Account account = getItem(position);
|
||||||
if (view == null) {
|
if (view == null) {
|
||||||
LayoutInflater inflater = (LayoutInflater) getContext()
|
LayoutInflater inflater = (LayoutInflater) getContext()
|
||||||
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||||
|
@ -34,21 +37,32 @@ public class AccountAdapter extends ArrayAdapter<Account> {
|
||||||
jid.setText(account.getJid().toBareJid().toString());
|
jid.setText(account.getJid().toBareJid().toString());
|
||||||
TextView statusView = (TextView) view.findViewById(R.id.account_status);
|
TextView statusView = (TextView) view.findViewById(R.id.account_status);
|
||||||
ImageView imageView = (ImageView) view.findViewById(R.id.account_image);
|
ImageView imageView = (ImageView) view.findViewById(R.id.account_image);
|
||||||
imageView.setImageBitmap(activity.avatarService().get(account,
|
imageView.setImageBitmap(activity.avatarService().get(account, activity.getPixel(48)));
|
||||||
activity.getPixel(48)));
|
statusView.setText(getContext().getString(account.getStatus().getReadableId()));
|
||||||
statusView.setText(getContext().getString(account.getStatus().getReadableId()));
|
switch (account.getStatus()) {
|
||||||
switch (account.getStatus()) {
|
case ONLINE:
|
||||||
case ONLINE:
|
statusView.setTextColor(activity.getOnlineColor());
|
||||||
statusView.setTextColor(activity.getOnlineColor());
|
break;
|
||||||
break;
|
case DISABLED:
|
||||||
case DISABLED:
|
case CONNECTING:
|
||||||
case CONNECTING:
|
statusView.setTextColor(activity.getSecondaryTextColor());
|
||||||
statusView.setTextColor(activity.getSecondaryTextColor());
|
break;
|
||||||
break;
|
default:
|
||||||
default:
|
statusView.setTextColor(activity.getWarningTextColor());
|
||||||
statusView.setTextColor(activity.getWarningTextColor());
|
break;
|
||||||
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;
|
return view;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,17 +46,10 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> {
|
||||||
}
|
}
|
||||||
Conversation conversation = getItem(position);
|
Conversation conversation = getItem(position);
|
||||||
if (this.activity instanceof ConversationActivity) {
|
if (this.activity instanceof ConversationActivity) {
|
||||||
ConversationActivity activity = (ConversationActivity) this.activity;
|
View swipeableItem = view.findViewById(R.id.swipeable_item);
|
||||||
if (!activity.isConversationsOverviewHideable()) {
|
ConversationActivity a = (ConversationActivity) this.activity;
|
||||||
if (conversation == activity.getSelectedConversation()) {
|
int c = !a.isConversationsOverviewHideable() && conversation == a.getSelectedConversation() ? a.getSecondaryBackgroundColor() : a.getPrimaryBackgroundColor();
|
||||||
view.setBackgroundColor(activity
|
swipeableItem.setBackgroundColor(c);
|
||||||
.getSecondaryBackgroundColor());
|
|
||||||
} else {
|
|
||||||
view.setBackgroundColor(Color.TRANSPARENT);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
view.setBackgroundColor(Color.TRANSPARENT);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
TextView convName = (TextView) view.findViewById(R.id.conversation_name);
|
TextView convName = (TextView) view.findViewById(R.id.conversation_name);
|
||||||
if (conversation.getMode() == Conversation.MODE_SINGLE || activity.useSubjectToIdentifyConference()) {
|
if (conversation.getMode() == Conversation.MODE_SINGLE || activity.useSubjectToIdentifyConference()) {
|
||||||
|
|
|
@ -91,6 +91,9 @@ public final class CryptoHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String prettifyFingerprint(String fingerprint) {
|
public static String prettifyFingerprint(String fingerprint) {
|
||||||
|
if (fingerprint.length() < 40) {
|
||||||
|
return fingerprint;
|
||||||
|
}
|
||||||
StringBuilder builder = new StringBuilder(fingerprint);
|
StringBuilder builder = new StringBuilder(fingerprint);
|
||||||
builder.insert(8, " ");
|
builder.insert(8, " ");
|
||||||
builder.insert(17, " ");
|
builder.insert(17, " ");
|
||||||
|
|
|
@ -20,7 +20,7 @@ public class GeoHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ArrayList<Intent> createGeoIntentsFromMessage(Message message) {
|
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());
|
Matcher matcher = GEO_URI.matcher(message.getBody());
|
||||||
if (!matcher.matches()) {
|
if (!matcher.matches()) {
|
||||||
return intents;
|
return intents;
|
||||||
|
|
|
@ -90,7 +90,7 @@ public class XmppConnection implements Runnable {
|
||||||
private boolean shouldBind = true;
|
private boolean shouldBind = true;
|
||||||
private boolean shouldAuthenticate = true;
|
private boolean shouldAuthenticate = true;
|
||||||
private Element streamFeatures;
|
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 String streamId = null;
|
||||||
private int smVersion = 3;
|
private int smVersion = 3;
|
||||||
|
@ -334,16 +334,23 @@ public class XmppConnection implements Runnable {
|
||||||
} catch (final NumberFormatException ignored) {
|
} catch (final NumberFormatException ignored) {
|
||||||
}
|
}
|
||||||
sendServiceDiscoveryInfo(account.getServer());
|
sendServiceDiscoveryInfo(account.getServer());
|
||||||
|
sendServiceDiscoveryInfo(account.getJid().toBareJid());
|
||||||
sendServiceDiscoveryItems(account.getServer());
|
sendServiceDiscoveryItems(account.getServer());
|
||||||
sendInitialPing();
|
sendInitialPing();
|
||||||
} else if (nextTag.isStart("r")) {
|
} else if (nextTag.isStart("r")) {
|
||||||
tagReader.readElement(nextTag);
|
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);
|
final AckPacket ack = new AckPacket(this.stanzasReceived, smVersion);
|
||||||
tagWriter.writeStanzaAsync(ack);
|
tagWriter.writeStanzaAsync(ack);
|
||||||
} else if (nextTag.isStart("a")) {
|
} else if (nextTag.isStart("a")) {
|
||||||
final Element ack = tagReader.readElement(nextTag);
|
final Element ack = tagReader.readElement(nextTag);
|
||||||
lastPacketReceived = SystemClock.elapsedRealtime();
|
lastPacketReceived = SystemClock.elapsedRealtime();
|
||||||
final int serverSequence = Integer.parseInt(ack.getAttribute("h"));
|
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);
|
final String msgId = this.messageReceipts.get(serverSequence);
|
||||||
if (msgId != null) {
|
if (msgId != null) {
|
||||||
if (this.acknowledgedListener != null) {
|
if (this.acknowledgedListener != null) {
|
||||||
|
@ -597,8 +604,10 @@ public class XmppConnection implements Runnable {
|
||||||
} else if (this.streamFeatures.hasChild("sm", "urn:xmpp:sm:"
|
} else if (this.streamFeatures.hasChild("sm", "urn:xmpp:sm:"
|
||||||
+ smVersion)
|
+ smVersion)
|
||||||
&& streamId != null) {
|
&& streamId != null) {
|
||||||
final ResumePacket resume = new ResumePacket(this.streamId,
|
if (Config.EXTENDED_SM_LOGGING) {
|
||||||
stanzasReceived, smVersion);
|
Log.d(Config.LOGTAG,account.getJid().toBareJid()+": resuming after stanza #"+stanzasReceived);
|
||||||
|
}
|
||||||
|
final ResumePacket resume = new ResumePacket(this.streamId, stanzasReceived, smVersion);
|
||||||
this.tagWriter.writeStanzaAsync(resume);
|
this.tagWriter.writeStanzaAsync(resume);
|
||||||
} else if (this.streamFeatures.hasChild("bind") && shouldBind) {
|
} else if (this.streamFeatures.hasChild("bind") && shouldBind) {
|
||||||
sendBindRequest();
|
sendBindRequest();
|
||||||
|
@ -734,6 +743,7 @@ public class XmppConnection implements Runnable {
|
||||||
features.blockListRequested = false;
|
features.blockListRequested = false;
|
||||||
disco.clear();
|
disco.clear();
|
||||||
sendServiceDiscoveryInfo(account.getServer());
|
sendServiceDiscoveryInfo(account.getServer());
|
||||||
|
sendServiceDiscoveryInfo(account.getJid().toBareJid());
|
||||||
sendServiceDiscoveryItems(account.getServer());
|
sendServiceDiscoveryItems(account.getServer());
|
||||||
if (bindListener != null) {
|
if (bindListener != null) {
|
||||||
bindListener.onBind(account);
|
bindListener.onBind(account);
|
||||||
|
@ -741,34 +751,35 @@ public class XmppConnection implements Runnable {
|
||||||
sendInitialPing();
|
sendInitialPing();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendServiceDiscoveryInfo(final Jid server) {
|
private void sendServiceDiscoveryInfo(final Jid jid) {
|
||||||
if (disco.containsKey(server.toDomainJid().toString())) {
|
if (disco.containsKey(jid)) {
|
||||||
if (account.getServer().equals(server.toDomainJid())) {
|
if (account.getServer().equals(jid)) {
|
||||||
enableAdvancedStreamFeatures();
|
enableAdvancedStreamFeatures();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
final IqPacket iq = new IqPacket(IqPacket.TYPE.GET);
|
final IqPacket iq = new IqPacket(IqPacket.TYPE.GET);
|
||||||
iq.setTo(server.toDomainJid());
|
iq.setTo(jid);
|
||||||
iq.query("http://jabber.org/protocol/disco#info");
|
iq.query("http://jabber.org/protocol/disco#info");
|
||||||
this.sendIqPacket(iq, new OnIqPacketReceived() {
|
this.sendIqPacket(iq, new OnIqPacketReceived() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onIqPacketReceived(final Account account, final IqPacket packet) {
|
public void onIqPacketReceived(final Account account, final IqPacket packet) {
|
||||||
final List<Element> elements = packet.query().getChildren();
|
final List<Element> elements = packet.query().getChildren();
|
||||||
final List<String> features = new ArrayList<>();
|
final Info info = new Info();
|
||||||
for (final Element element : elements) {
|
for (final Element element : elements) {
|
||||||
if (element.getName().equals("identity")) {
|
if (element.getName().equals("identity")) {
|
||||||
if ("irc".equals(element.getAttribute("type"))) {
|
String type = element.getAttribute("type");
|
||||||
//add fake feature to not confuse irc and real muc
|
String category = element.getAttribute("category");
|
||||||
features.add("siacs:no:muc");
|
if (type != null && category != null) {
|
||||||
|
info.identities.add(new Pair<>(category,type));
|
||||||
}
|
}
|
||||||
} else if (element.getName().equals("feature")) {
|
} 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();
|
enableAdvancedStreamFeatures();
|
||||||
for (final OnAdvancedStreamFeaturesLoaded listener : advancedStreamFeaturesLoadedListeners) {
|
for (final OnAdvancedStreamFeaturesLoaded listener : advancedStreamFeaturesLoadedListeners) {
|
||||||
listener.onAdvancedStreamFeaturesAvailable(account);
|
listener.onAdvancedStreamFeaturesAvailable(account);
|
||||||
|
@ -784,7 +795,7 @@ public class XmppConnection implements Runnable {
|
||||||
sendEnableCarbons();
|
sendEnableCarbons();
|
||||||
}
|
}
|
||||||
if (getFeatures().blocking() && !features.blockListRequested) {
|
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());
|
this.sendIqPacket(getIqGenerator().generateGetBlockList(), mXmppConnectionService.getIqParser());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -891,7 +902,9 @@ public class XmppConnection implements Runnable {
|
||||||
}
|
}
|
||||||
tagWriter.writeStanzaAsync(packet);
|
tagWriter.writeStanzaAsync(packet);
|
||||||
if (packet instanceof MessagePacket && packet.getId() != null && this.streamId != null) {
|
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());
|
this.messageReceipts.put(stanzasSent, packet.getId());
|
||||||
tagWriter.writeStanzaAsync(new RequestPacket(this.smVersion));
|
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) {
|
public List<String> findDiscoItemsByFeature(final String feature) {
|
||||||
final List<String> items = new ArrayList<>();
|
final List<String> items = new ArrayList<>();
|
||||||
for (final Entry<String, List<String>> cursor : disco.entrySet()) {
|
for (final Entry<Jid, Info> cursor : disco.entrySet()) {
|
||||||
if (cursor.getValue().contains(feature)) {
|
if (cursor.getValue().features.contains(feature)) {
|
||||||
items.add(cursor.getKey());
|
items.add(cursor.getKey().toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return items;
|
return items;
|
||||||
|
@ -1004,10 +1021,12 @@ public class XmppConnection implements Runnable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getMucServer() {
|
public String getMucServer() {
|
||||||
for (final Entry<String, List<String>> cursor : disco.entrySet()) {
|
for (final Entry<Jid, Info> cursor : disco.entrySet()) {
|
||||||
final List<String> value = cursor.getValue();
|
final Info value = cursor.getValue();
|
||||||
if (value.contains("http://jabber.org/protocol/muc") && !value.contains("jabber:iq:gateway") && !value.contains("siacs:no:muc")) {
|
if (value.features.contains("http://jabber.org/protocol/muc")
|
||||||
return cursor.getKey();
|
&& !value.features.contains("jabber:iq:gateway")
|
||||||
|
&& !value.identities.contains(new Pair<>("conference","irc"))) {
|
||||||
|
return cursor.getKey().toString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -1062,6 +1081,11 @@ public class XmppConnection implements Runnable {
|
||||||
this.lastConnect = 0;
|
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 {
|
public class Features {
|
||||||
XmppConnection connection;
|
XmppConnection connection;
|
||||||
private boolean carbonsEnabled = false;
|
private boolean carbonsEnabled = false;
|
||||||
|
@ -1073,8 +1097,8 @@ public class XmppConnection implements Runnable {
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean hasDiscoFeature(final Jid server, final String feature) {
|
private boolean hasDiscoFeature(final Jid server, final String feature) {
|
||||||
return connection.disco.containsKey(server.toDomainJid().toString()) &&
|
return connection.disco.containsKey(server) &&
|
||||||
connection.disco.get(server.toDomainJid().toString()).contains(feature);
|
connection.disco.get(server).features.contains(feature);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean carbons() {
|
public boolean carbons() {
|
||||||
|
@ -1090,24 +1114,35 @@ public class XmppConnection implements Runnable {
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean sm() {
|
public boolean sm() {
|
||||||
return streamId != null;
|
return streamId != null
|
||||||
|
|| (connection.streamFeatures != null && connection.streamFeatures.hasChild("sm"));
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean csi() {
|
public boolean csi() {
|
||||||
return connection.streamFeatures != null && connection.streamFeatures.hasChild("csi", "urn:xmpp:csi:0");
|
return connection.streamFeatures != null && connection.streamFeatures.hasChild("csi", "urn:xmpp:csi:0");
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean pubsub() {
|
public boolean pep() {
|
||||||
return hasDiscoFeature(account.getServer(),
|
final Pair<String,String> needle = new Pair<>("pubsub","pep");
|
||||||
"http://jabber.org/protocol/pubsub#publish");
|
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() {
|
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() {
|
public boolean advancedStreamFeaturesLoaded() {
|
||||||
return disco.containsKey(account.getServer().toString());
|
return disco.containsKey(account.getServer());
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean rosterVersioning() {
|
public boolean rosterVersioning() {
|
||||||
|
|
|
@ -192,7 +192,7 @@ public class JingleConnection implements Downloadable {
|
||||||
} else {
|
} else {
|
||||||
response = packet.generateResponse(IqPacket.TYPE.ERROR);
|
response = packet.generateResponse(IqPacket.TYPE.ERROR);
|
||||||
}
|
}
|
||||||
account.getXmppConnection().sendIqPacket(response, null);
|
mXmppConnectionService.sendIqPacket(account,response,null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void init(Message message) {
|
public void init(Message message) {
|
||||||
|
@ -317,7 +317,7 @@ public class JingleConnection implements Downloadable {
|
||||||
message.setBody(Long.toString(size));
|
message.setBody(Long.toString(size));
|
||||||
conversation.add(message);
|
conversation.add(message);
|
||||||
mXmppConnectionService.updateConversationUi();
|
mXmppConnectionService.updateConversationUi();
|
||||||
if (size <= this.mJingleConnectionManager
|
if (size < this.mJingleConnectionManager
|
||||||
.getAutoAcceptFileSize()) {
|
.getAutoAcceptFileSize()) {
|
||||||
Log.d(Config.LOGTAG, "auto accepting file from "
|
Log.d(Config.LOGTAG, "auto accepting file from "
|
||||||
+ packet.getFrom());
|
+ packet.getFrom());
|
||||||
|
@ -459,11 +459,11 @@ public class JingleConnection implements Downloadable {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendJinglePacket(JinglePacket packet) {
|
private void sendJinglePacket(JinglePacket packet) {
|
||||||
account.getXmppConnection().sendIqPacket(packet, responseListener);
|
mXmppConnectionService.sendIqPacket(account,packet,responseListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendJinglePacket(JinglePacket packet, OnIqPacketReceived callback) {
|
private void sendJinglePacket(JinglePacket packet, OnIqPacketReceived callback) {
|
||||||
account.getXmppConnection().sendIqPacket(packet,callback);
|
mXmppConnectionService.sendIqPacket(account,packet,callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean receiveAccept(JinglePacket packet) {
|
private boolean receiveAccept(JinglePacket packet) {
|
||||||
|
@ -556,7 +556,7 @@ public class JingleConnection implements Downloadable {
|
||||||
.setAttribute("sid", this.getSessionId());
|
.setAttribute("sid", this.getSessionId());
|
||||||
activation.query().addChild("activate")
|
activation.query().addChild("activate")
|
||||||
.setContent(this.getCounterPart().toString());
|
.setContent(this.getCounterPart().toString());
|
||||||
this.account.getXmppConnection().sendIqPacket(activation,
|
mXmppConnectionService.sendIqPacket(account,activation,
|
||||||
new OnIqPacketReceived() {
|
new OnIqPacketReceived() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -11,6 +11,7 @@ import android.util.Base64;
|
||||||
|
|
||||||
import eu.siacs.conversations.entities.Account;
|
import eu.siacs.conversations.entities.Account;
|
||||||
import eu.siacs.conversations.entities.DownloadableFile;
|
import eu.siacs.conversations.entities.DownloadableFile;
|
||||||
|
import eu.siacs.conversations.persistance.FileBackend;
|
||||||
import eu.siacs.conversations.utils.CryptoHelper;
|
import eu.siacs.conversations.utils.CryptoHelper;
|
||||||
import eu.siacs.conversations.xml.Element;
|
import eu.siacs.conversations.xml.Element;
|
||||||
import eu.siacs.conversations.xmpp.OnIqPacketReceived;
|
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));
|
connection.updateProgress((int) ((((double) (this.fileSize - this.remainingSize)) / this.fileSize) * 100));
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
FileBackend.close(fileInputStream);
|
||||||
this.onFileTransmissionStatusChanged.onFileTransferAborted();
|
this.onFileTransmissionStatusChanged.onFileTransferAborted();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -198,6 +200,7 @@ public class JingleInbandTransport extends JingleTransport {
|
||||||
connection.updateProgress((int) ((((double) (this.fileSize - this.remainingSize)) / this.fileSize) * 100));
|
connection.updateProgress((int) ((((double) (this.fileSize - this.remainingSize)) / this.fileSize) * 100));
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
FileBackend.close(fileOutputStream);
|
||||||
this.onFileTransmissionStatusChanged.onFileTransferAborted();
|
this.onFileTransmissionStatusChanged.onFileTransferAborted();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -207,6 +210,7 @@ public class JingleInbandTransport extends JingleTransport {
|
||||||
if (!established) {
|
if (!established) {
|
||||||
established = true;
|
established = true;
|
||||||
connected = true;
|
connected = true;
|
||||||
|
this.receiveNextBlock("");
|
||||||
this.account.getXmppConnection().sendIqPacket(
|
this.account.getXmppConnection().sendIqPacket(
|
||||||
packet.generateResponse(IqPacket.TYPE.RESULT), null);
|
packet.generateResponse(IqPacket.TYPE.RESULT), null);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -11,6 +11,7 @@ import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
import eu.siacs.conversations.entities.DownloadableFile;
|
import eu.siacs.conversations.entities.DownloadableFile;
|
||||||
|
import eu.siacs.conversations.persistance.FileBackend;
|
||||||
import eu.siacs.conversations.utils.CryptoHelper;
|
import eu.siacs.conversations.utils.CryptoHelper;
|
||||||
|
|
||||||
public class JingleSocks5Transport extends JingleTransport {
|
public class JingleSocks5Transport extends JingleTransport {
|
||||||
|
@ -126,25 +127,19 @@ public class JingleSocks5Transport extends JingleTransport {
|
||||||
} catch (NoSuchAlgorithmException e) {
|
} catch (NoSuchAlgorithmException e) {
|
||||||
callback.onFileTransferAborted();
|
callback.onFileTransferAborted();
|
||||||
} finally {
|
} finally {
|
||||||
try {
|
FileBackend.close(fileInputStream);
|
||||||
if (fileInputStream != null) {
|
|
||||||
fileInputStream.close();
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
callback.onFileTransferAborted();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).start();
|
}).start();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void receive(final DownloadableFile file,
|
public void receive(final DownloadableFile file, final OnFileTransmissionStatusChanged callback) {
|
||||||
final OnFileTransmissionStatusChanged callback) {
|
|
||||||
new Thread(new Runnable() {
|
new Thread(new Runnable() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
|
OutputStream fileOutputStream = null;
|
||||||
try {
|
try {
|
||||||
MessageDigest digest = MessageDigest.getInstance("SHA-1");
|
MessageDigest digest = MessageDigest.getInstance("SHA-1");
|
||||||
digest.reset();
|
digest.reset();
|
||||||
|
@ -152,7 +147,7 @@ public class JingleSocks5Transport extends JingleTransport {
|
||||||
socket.setSoTimeout(30000);
|
socket.setSoTimeout(30000);
|
||||||
file.getParentFile().mkdirs();
|
file.getParentFile().mkdirs();
|
||||||
file.createNewFile();
|
file.createNewFile();
|
||||||
OutputStream fileOutputStream = file.createOutputStream();
|
fileOutputStream = file.createOutputStream();
|
||||||
if (fileOutputStream == null) {
|
if (fileOutputStream == null) {
|
||||||
callback.onFileTransferAborted();
|
callback.onFileTransferAborted();
|
||||||
return;
|
return;
|
||||||
|
@ -183,6 +178,8 @@ public class JingleSocks5Transport extends JingleTransport {
|
||||||
callback.onFileTransferAborted();
|
callback.onFileTransferAborted();
|
||||||
} catch (NoSuchAlgorithmException e) {
|
} catch (NoSuchAlgorithmException e) {
|
||||||
callback.onFileTransferAborted();
|
callback.onFileTransferAborted();
|
||||||
|
} finally {
|
||||||
|
FileBackend.close(fileOutputStream);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}).start();
|
}).start();
|
||||||
|
|
After Width: | Height: | Size: 223 B |
After Width: | Height: | Size: 452 B |
Before Width: | Height: | Size: 363 B |
Before Width: | Height: | Size: 287 B After Width: | Height: | Size: 287 B |
Before Width: | Height: | Size: 348 B |
After Width: | Height: | Size: 248 B |
After Width: | Height: | Size: 246 B |
Before Width: | Height: | Size: 341 B After Width: | Height: | Size: 341 B |
Before Width: | Height: | Size: 419 B |
After Width: | Height: | Size: 339 B |
After Width: | Height: | Size: 396 B |
After Width: | Height: | Size: 417 B |
After Width: | Height: | Size: 300 B |
After Width: | Height: | Size: 400 B |
Before Width: | Height: | Size: 743 B |
After Width: | Height: | Size: 399 B |
After Width: | Height: | Size: 383 B |
Before Width: | Height: | Size: 875 B |
Before Width: | Height: | Size: 217 B |
After Width: | Height: | Size: 504 B |
Before Width: | Height: | Size: 572 B After Width: | Height: | Size: 572 B |
Before Width: | Height: | Size: 780 B |
Before Width: | Height: | Size: 421 B After Width: | Height: | Size: 421 B |
Before Width: | Height: | Size: 540 B |
After Width: | Height: | Size: 174 B |
After Width: | Height: | Size: 332 B |
Before Width: | Height: | Size: 290 B |
After Width: | Height: | Size: 217 B |
After Width: | Height: | Size: 199 B |
After Width: | Height: | Size: 197 B |
After Width: | Height: | Size: 276 B |
After Width: | Height: | Size: 272 B |
After Width: | Height: | Size: 313 B |
After Width: | Height: | Size: 296 B |
After Width: | Height: | Size: 226 B |
After Width: | Height: | Size: 290 B |
Before Width: | Height: | Size: 449 B |
After Width: | Height: | Size: 296 B |
After Width: | Height: | Size: 289 B |
Before Width: | Height: | Size: 637 B |
Before Width: | Height: | Size: 197 B |
After Width: | Height: | Size: 346 B |
After Width: | Height: | Size: 423 B |
After Width: | Height: | Size: 328 B |
Before Width: | Height: | Size: 198 B After Width: | Height: | Size: 198 B |
Before Width: | Height: | Size: 576 B After Width: | Height: | Size: 576 B |
Before Width: | Height: | Size: 533 B |
After Width: | Height: | Size: 320 B |
After Width: | Height: | Size: 271 B |
Before Width: | Height: | Size: 270 B After Width: | Height: | Size: 270 B |
After Width: | Height: | Size: 379 B |
Before Width: | Height: | Size: 378 B After Width: | Height: | Size: 378 B |
Before Width: | Height: | Size: 484 B After Width: | Height: | Size: 484 B |
Before Width: | Height: | Size: 464 B After Width: | Height: | Size: 464 B |
Before Width: | Height: | Size: 330 B After Width: | Height: | Size: 330 B |
Before Width: | Height: | Size: 513 B After Width: | Height: | Size: 513 B |
Before Width: | Height: | Size: 948 B |
After Width: | Height: | Size: 465 B |
Before Width: | Height: | Size: 423 B After Width: | Height: | Size: 423 B |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 212 B |
Before Width: | Height: | Size: 591 B After Width: | Height: | Size: 591 B |
After Width: | Height: | Size: 704 B |
After Width: | Height: | Size: 460 B |
Before Width: | Height: | Size: 222 B After Width: | Height: | Size: 222 B |
Before Width: | Height: | Size: 356 B |
Before Width: | Height: | Size: 870 B After Width: | Height: | Size: 870 B |
Before Width: | Height: | Size: 1.6 KiB |