diff options
Diffstat (limited to 'src/main/java')
20 files changed, 315 insertions, 95 deletions
diff --git a/src/main/java/de/thedevstack/conversationsplus/ConversationsPlusPreferences.java b/src/main/java/de/thedevstack/conversationsplus/ConversationsPlusPreferences.java index d22a10d6..70eec7a2 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ConversationsPlusPreferences.java +++ b/src/main/java/de/thedevstack/conversationsplus/ConversationsPlusPreferences.java @@ -237,6 +237,14 @@ public class ConversationsPlusPreferences extends Settings { return getBoolean("allow_message_correction", true); } + public static boolean returnToPrevious() { + return getBoolean("return_to_previous", false); + } + + public static boolean led() { + return getBoolean("led", true); + } + private ConversationsPlusPreferences(SharedPreferences sharedPreferences) { this.sharedPreferences = sharedPreferences; } diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/AvatarUtil.java b/src/main/java/de/thedevstack/conversationsplus/utils/AvatarUtil.java index 63dc320e..b6ffd570 100644 --- a/src/main/java/de/thedevstack/conversationsplus/utils/AvatarUtil.java +++ b/src/main/java/de/thedevstack/conversationsplus/utils/AvatarUtil.java @@ -1,12 +1,14 @@ package de.thedevstack.conversationsplus.utils; import android.graphics.Bitmap; +import android.graphics.BitmapFactory; import android.net.Uri; import android.util.Base64; import android.util.Base64OutputStream; import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; @@ -60,6 +62,44 @@ public final class AvatarUtil { } catch (IOException e) { return null; } + + } + + public static Avatar getStoredPepAvatar(String hash) { + if (hash == null) { + return null; + } + Avatar avatar = new Avatar(); + File file = new File(getAvatarPath(hash)); + FileInputStream is = null; + try { + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeFile(file.getAbsolutePath(), options); + is = new FileInputStream(file); + ByteArrayOutputStream mByteArrayOutputStream = new ByteArrayOutputStream(); + Base64OutputStream mBase64OutputStream = new Base64OutputStream(mByteArrayOutputStream, Base64.DEFAULT); + MessageDigest digest = MessageDigest.getInstance("SHA-1"); + DigestOutputStream os = new DigestOutputStream(mBase64OutputStream, digest); + byte[] buffer = new byte[4096]; + int length; + while ((length = is.read(buffer)) > 0) { + os.write(buffer, 0, length); + } + os.flush(); + os.close(); + avatar.sha1sum = CryptoHelper.bytesToHex(digest.digest()); + avatar.image = new String(mByteArrayOutputStream.toByteArray()); + avatar.height = options.outHeight; + avatar.width = options.outWidth; + return avatar; + } catch (IOException e) { + return null; + } catch (NoSuchAlgorithmException e) { + return null; + } finally { + StreamUtil.close(is); + } } /** diff --git a/src/main/java/eu/siacs/conversations/Config.java b/src/main/java/eu/siacs/conversations/Config.java index 25f08f30..4d5e3d62 100644 --- a/src/main/java/eu/siacs/conversations/Config.java +++ b/src/main/java/eu/siacs/conversations/Config.java @@ -48,8 +48,7 @@ public final class Config { public static final boolean ALLOW_NON_TLS_CONNECTIONS = false; //very dangerous. you should have a good reason to set this to true public static final boolean HIDE_MESSAGE_TEXT_IN_NOTIFICATION = false; public static final boolean SHOW_CONNECTED_ACCOUNTS = false; //show number of connected accounts in foreground notification - - public static final boolean ALWAYS_NOTIFY_BY_DEFAULT = false; + public static final boolean SHOW_DISABLE_FOREGROUND = true; //if set to true the foreground notification has a button to disable it public static final boolean LEGACY_NAMESPACE_HTTP_UPLOAD = false; diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java index e490ac64..a066faad 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlService.java @@ -115,4 +115,6 @@ public interface AxolotlService extends OnAdvancedStreamFeaturesLoaded { boolean hasPendingKeyFetches(Account account, List<Jid> jids); void prepareKeyTransportMessage(Conversation conversation, OnMessageCreatedCallback onMessageCreatedCallback); + + void resetBrokenness(); } diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlServiceImpl.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlServiceImpl.java index 73974652..364898b6 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlServiceImpl.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlServiceImpl.java @@ -49,7 +49,7 @@ import eu.siacs.conversations.xmpp.jid.InvalidJidException; import eu.siacs.conversations.xmpp.jid.Jid; import eu.siacs.conversations.xmpp.stanzas.IqPacket; -public class AxolotlServiceImpl implements AxolotlService { +public class AxolotlServiceImpl implements OnAdvancedStreamFeaturesLoaded, AxolotlService { public static final int publishTriesThreshold = 3; @@ -224,6 +224,7 @@ public class AxolotlServiceImpl implements AxolotlService { return axolotlStore.getIdentityKeyPair().getPublicKey().getFingerprint().replaceAll("\\s", ""); } + @Override public Set<IdentityKey> getKeysWithTrust(XmppAxolotlSession.Trust trust) { return axolotlStore.getContactKeysWithTrust(account.getJid().toBareJid().toString(), trust); } @@ -242,12 +243,10 @@ public class AxolotlServiceImpl implements AxolotlService { return keys; } - @Override public long getNumTrustedKeys(Jid jid) { return axolotlStore.getContactNumTrustedKeys(jid.toBareJid().toString()); } - @Override public boolean anyTargetHasNoTrustedKeys(List<Jid> jids) { for(Jid jid : jids) { if (axolotlStore.getContactNumTrustedKeys(jid.toBareJid().toString()) == 0) { @@ -303,6 +302,11 @@ public class AxolotlServiceImpl implements AxolotlService { return this.pepBroken; } + public void resetBrokenness() { + this.pepBroken = false; + numPublishTriesOnEmptyPep = 0; + } + public void regenerateKeys(boolean wipeOther) { axolotlStore.regenerate(); sessions.clear(); @@ -437,7 +441,8 @@ public class AxolotlServiceImpl implements AxolotlService { mXmppConnectionService.sendIqPacket(account, publish, new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket packet) { - if (packet.getType() != IqPacket.TYPE.RESULT) { + if (packet.getType() == IqPacket.TYPE.ERROR) { + pepBroken = true; Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing own device id" + packet.findChild("error")); } } @@ -601,14 +606,14 @@ public class AxolotlServiceImpl implements AxolotlService { Log.d(Config.LOGTAG, getLogprefix(account) + "Announcing device " + getOwnDeviceId()); publishOwnDeviceIdIfNeeded(); } - } else { + } else if (packet.getType() == IqPacket.TYPE.ERROR) { + pepBroken = true; Log.d(Config.LOGTAG, getLogprefix(account) + "Error received while publishing bundle: " + packet.findChild("error")); } } }); } - @Override public boolean isConversationAxolotlCapable(Conversation conversation) { final List<Jid> jids = getCryptoTargets(conversation); for(Jid jid : jids) { @@ -619,7 +624,6 @@ public class AxolotlServiceImpl implements AxolotlService { return jids.size() > 0; } - @Override public List<Jid> getCryptoTargets(Conversation conversation) { final List<Jid> jids; if (conversation.getMode() == Conversation.MODE_SINGLE) { @@ -871,7 +875,6 @@ public class AxolotlServiceImpl implements AxolotlService { return verified; } - @Override public boolean hasPendingKeyFetches(Account account, List<Jid> jids) { AxolotlAddress ownAddress = new AxolotlAddress(account.getJid().toBareJid().toString(), 0); if (fetchStatusMap.getAll(ownAddress).containsValue(FetchStatus.PENDING)) { @@ -949,7 +952,6 @@ public class AxolotlServiceImpl implements AxolotlService { }); } - @Override public void prepareKeyTransportMessage(final Conversation conversation, final OnMessageCreatedCallback onMessageCreatedCallback) { executor.execute(new Runnable() { @Override diff --git a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlServiceStub.java b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlServiceStub.java index b0228d34..0152d02a 100644 --- a/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlServiceStub.java +++ b/src/main/java/eu/siacs/conversations/crypto/axolotl/AxolotlServiceStub.java @@ -204,4 +204,9 @@ public class AxolotlServiceStub implements AxolotlService { public void onAdvancedStreamFeaturesAvailable(Account account) { } + + @Override + public void resetBrokenness() { + + } } diff --git a/src/main/java/eu/siacs/conversations/entities/Conversation.java b/src/main/java/eu/siacs/conversations/entities/Conversation.java index 62f976b3..ab0e4966 100644 --- a/src/main/java/eu/siacs/conversations/entities/Conversation.java +++ b/src/main/java/eu/siacs/conversations/entities/Conversation.java @@ -326,6 +326,10 @@ public class Conversation extends AbstractEntity implements Blockable { return this.correctingMessage; } + public boolean withSelf() { + return getContact().isSelf(); + } + public interface OnMessageFound { void onMessageFound(final Message message); } @@ -791,7 +795,7 @@ public class Conversation extends AbstractEntity implements Blockable { } public boolean alwaysNotify() { - return mode == MODE_SINGLE || getBooleanAttribute(ATTRIBUTE_ALWAYS_NOTIFY, Config.ALWAYS_NOTIFY_BY_DEFAULT || isPnNA()); + return mode == MODE_SINGLE || getBooleanAttribute(ATTRIBUTE_ALWAYS_NOTIFY, true); } public boolean setAttribute(String key, String value) { diff --git a/src/main/java/eu/siacs/conversations/entities/MucOptions.java b/src/main/java/eu/siacs/conversations/entities/MucOptions.java index db27810f..7681a3d4 100644 --- a/src/main/java/eu/siacs/conversations/entities/MucOptions.java +++ b/src/main/java/eu/siacs/conversations/entities/MucOptions.java @@ -469,6 +469,9 @@ public class MucOptions { } public Jid getTrueCounterpart(String name) { + if (name.equals(getSelf().getName())) { + return account.getJid().toBareJid(); + } User user = findUser(name); return user == null ? null : user.getJid(); } diff --git a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java index 94347985..2cd59296 100644 --- a/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java +++ b/src/main/java/eu/siacs/conversations/http/HttpUploadConnection.java @@ -97,7 +97,6 @@ public class HttpUploadConnection implements Transferable { public void init(Message message, boolean delay) { this.message = message; - this.message.setHttpUploaded(true); this.account = message.getConversation().getAccount(); this.file = FileBackend.getFile(message, false); this.mime = this.file.getMimeType(); diff --git a/src/main/java/eu/siacs/conversations/services/AvatarService.java b/src/main/java/eu/siacs/conversations/services/AvatarService.java index f4cbdf3d..16d9539a 100644 --- a/src/main/java/eu/siacs/conversations/services/AvatarService.java +++ b/src/main/java/eu/siacs/conversations/services/AvatarService.java @@ -31,14 +31,17 @@ import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.MucOptions; import eu.siacs.conversations.generator.IqGenerator; import eu.siacs.conversations.persistance.DatabaseBackend; +import eu.siacs.conversations.persistance.FileBackend; import eu.siacs.conversations.ui.UiCallback; import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.xml.Element; +import eu.siacs.conversations.xmpp.OnAdvancedStreamFeaturesLoaded; import eu.siacs.conversations.xmpp.OnIqPacketReceived; +import eu.siacs.conversations.xmpp.XmppConnection; import eu.siacs.conversations.xmpp.pep.Avatar; import eu.siacs.conversations.xmpp.stanzas.IqPacket; -public class AvatarService { +public class AvatarService implements OnAdvancedStreamFeaturesLoaded { private static final int FG_COLOR = 0xFFFAFAFA; private static final int TRANSPARENT = 0x00000000; @@ -408,6 +411,17 @@ public class AvatarService { return true; } + @Override + public void onAdvancedStreamFeaturesAvailable(Account account) { + XmppConnection.Features features = account.getXmppConnection().getFeatures(); + if (features.pep() && !features.pepPersistent()) { + Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": has pep but is not persistent"); + if (account.getAvatar() != null) { + republishAvatarIfNeeded(account); + } + } + } + public void publishAvatar(final Account account, final Uri image, final UiCallback<Avatar> callback) { @@ -428,47 +442,79 @@ public class AvatarService { callback.error(R.string.error_saving_avatar, avatar); return; } + sendAndReceiveIqPackages(avatar, account, callback); + } else { + callback.error(R.string.error_publish_avatar_converting, null); + } + } + + public void publishAvatar(Account account, final Avatar avatar, final UiCallback<Avatar> callback) { final IqPacket packet = AvatarPacketGenerator.generatePublishAvatarPacket(avatar); XmppSendUtil.sendIqPacket(account, packet, new OnIqPacketReceived() { @Override public void onIqPacketReceived(Account account, IqPacket result) { if (result.getType() == IqPacket.TYPE.RESULT) { - final IqPacket packet = AvatarPacketGenerator.generatePublishAvatarMetadataPacket(avatar); - XmppSendUtil.sendIqPacket(account, packet, new OnIqPacketReceived() { - - @Override - public void onIqPacketReceived(Account account, - IqPacket result) { - if (result.getType() == IqPacket.TYPE.RESULT) { - if (account.setAvatar(avatar.getFilename())) { - AvatarService.getInstance().clear(account); - DatabaseBackend.getInstance(ConversationsPlusApplication.getAppContext()).updateAccount(account); - } - callback.success(avatar); + sendAndReceiveIqPackages(avatar, account, callback); } else { + if (callback != null) { callback.error( R.string.error_publish_avatar_server_reject, avatar); } } - }); - } else { - callback.error( - R.string.error_publish_avatar_server_reject, - avatar); - } } }); - } else { - callback.error(R.string.error_publish_avatar_converting, null); - } } private static String generateFetchKey(Account account, final Avatar avatar) { return account.getJid().toBareJid()+"_"+avatar.owner+"_"+avatar.sha1sum; } + public void republishAvatarIfNeeded(Account account) { + if (account.getAxolotlService().isPepBroken()) { + Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": skipping republication of avatar because pep is broken"); + return; + } + IqPacket packet = AvatarPacketGenerator.generateRetrieveAvatarMetadataPacket(null); + XmppSendUtil.sendIqPacket(account, packet, new OnIqPacketReceived() { + + private Avatar parseAvatar(IqPacket packet) { + Element pubsub = packet.findChild("pubsub", "http://jabber.org/protocol/pubsub"); + if (pubsub != null) { + Element items = pubsub.findChild("items"); + if (items != null) { + return Avatar.parseMetadata(items); + } + } + return null; + } + + private boolean errorIsItemNotFound(IqPacket packet) { + Element error = packet.findChild("error"); + return packet.getType() == IqPacket.TYPE.ERROR + && error != null + && error.hasChild("item-not-found"); + } + + @Override + public void onIqPacketReceived(Account account, IqPacket packet) { + if (packet.getType() == IqPacket.TYPE.RESULT || errorIsItemNotFound(packet)) { + Avatar serverAvatar = parseAvatar(packet); + if (serverAvatar == null && account.getAvatar() != null) { + Avatar avatar = AvatarUtil.getStoredPepAvatar(account.getAvatar()); + if (avatar != null) { + Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": avatar on server was null. republishing"); + publishAvatar(account, AvatarUtil.getStoredPepAvatar(account.getAvatar()), null); + } else { + Logging.e(Config.LOGTAG, account.getJid().toBareJid()+": error rereading avatar"); + } + } + } + } + }); + } + public void fetchAvatar(Account account, final Avatar avatar, final UiCallback<Avatar> callback) { final String KEY = generateFetchKey(account, avatar); synchronized(this.mInProgressAvatarFetches) { @@ -624,4 +670,40 @@ public class AvatarService { } } } + + private void sendAndReceiveIqPackages(final Avatar avatar, final Account account, final UiCallback callback) { + + final IqPacket packet = AvatarPacketGenerator.generatePublishAvatarPacket(avatar); + XmppSendUtil.sendIqPacket(account, packet, new OnIqPacketReceived() { + + @Override + public void onIqPacketReceived(Account account, IqPacket result) { + if (result.getType() == IqPacket.TYPE.RESULT) { + final IqPacket packet = AvatarPacketGenerator.generatePublishAvatarMetadataPacket(avatar); + XmppSendUtil.sendIqPacket(account, packet, new OnIqPacketReceived() { + + @Override + public void onIqPacketReceived(Account account, + IqPacket result) { + if (result.getType() == IqPacket.TYPE.RESULT) { + if (account.setAvatar(avatar.getFilename())) { + AvatarService.getInstance().clear(account); + DatabaseBackend.getInstance(ConversationsPlusApplication.getAppContext()).updateAccount(account); + } + callback.success(avatar); + } else { + callback.error( + R.string.error_publish_avatar_server_reject, + avatar); + } + } + }); + } else { + callback.error( + R.string.error_publish_avatar_server_reject, + avatar); + } + } + }); + } } diff --git a/src/main/java/eu/siacs/conversations/services/ContactChooserTargetService.java b/src/main/java/eu/siacs/conversations/services/ContactChooserTargetService.java index 52894f07..e59c03d9 100644 --- a/src/main/java/eu/siacs/conversations/services/ContactChooserTargetService.java +++ b/src/main/java/eu/siacs/conversations/services/ContactChooserTargetService.java @@ -48,7 +48,7 @@ public class ContactChooserTargetService extends ChooserTargetService implements final Conversation conversation = conversations.get(i); final String name = conversation.getName(); final Icon icon = Icon.createWithBitmap(AvatarService.getInstance().get(conversation, pixel)); - final float score = (1.0f / MAX_TARGETS) * i; + final float score = 1 - (1.0f / MAX_TARGETS) * i; final Bundle extras = new Bundle(); extras.putString("uuid", conversation.getUuid()); chooserTargets.add(new ChooserTarget(name, icon, score, componentName, extras)); diff --git a/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java b/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java index 40dbf1f5..613d7150 100644 --- a/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java +++ b/src/main/java/eu/siacs/conversations/services/MessageArchiveService.java @@ -45,8 +45,10 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { } } } - long startCatchup = getLastMessageTransmitted(account); + Pair<Long,String> pair = mXmppConnectionService.databaseBackend.getLastMessageReceived(account); + long startCatchup = pair == null ? 0 : pair.first; long endCatchup = account.getXmppConnection().getLastSessionEstablished(); + final Query query; if (startCatchup == 0) { return; } else if (endCatchup - startCatchup >= Config.MAM_MAX_CATCHUP) { @@ -57,8 +59,11 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { this.query(conversation,startCatchup); } } + query = new Query(account, startCatchup, endCatchup); + } else { + query = new Query(account, startCatchup, endCatchup); + query.reference = pair.second; } - final Query query = new Query(account, startCatchup, endCatchup); this.queries.add(query); this.execute(query); } @@ -75,11 +80,6 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { } } - private long getLastMessageTransmitted(final Account account) { - Pair<Long,String> pair = mXmppConnectionService.databaseBackend.getLastMessageReceived(account); - return pair == null ? 0 : pair.first; - } - public Query query(final Conversation conversation) { if (conversation.getLastMessageTransmitted() < 0 && conversation.countMessages() == 0) { return query(conversation, diff --git a/src/main/java/eu/siacs/conversations/services/NotificationService.java b/src/main/java/eu/siacs/conversations/services/NotificationService.java index c2e1b098..5ae7e9f1 100644 --- a/src/main/java/eu/siacs/conversations/services/NotificationService.java +++ b/src/main/java/eu/siacs/conversations/services/NotificationService.java @@ -176,6 +176,7 @@ public class NotificationService { final String ringtone = ConversationsPlusPreferences.notificationRingtone(); final boolean vibrate = ConversationsPlusPreferences.vibrateOnNotification(); + final boolean led = ConversationsPlusPreferences.led(); if (notifications.size() == 0) { notificationManager.cancel(NOTIFICATION_ID); @@ -206,7 +207,9 @@ public class NotificationService { mBuilder.setDefaults(0); mBuilder.setSmallIcon(R.drawable.ic_notification); mBuilder.setDeleteIntent(createDeleteIntent()); + if (led) { mBuilder.setLights(Settings.LED_COLOR, 2000, 4000); + } final Notification notification = mBuilder.build(); notificationManager.notify(NOTIFICATION_ID, notification); } @@ -519,9 +522,11 @@ public class NotificationService { cancelIcon = R.drawable.ic_action_cancel; } mBuilder.setSmallIcon(R.drawable.ic_link_white_24dp); + if (Config.SHOW_DISABLE_FOREGROUND) { mBuilder.addAction(cancelIcon, mXmppConnectionService.getString(R.string.disable_foreground_service), createDisableForeground()); + } return mBuilder.build(); } diff --git a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java index 95d2781c..4180ce55 100644 --- a/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java +++ b/src/main/java/eu/siacs/conversations/services/XmppConnectionService.java @@ -455,7 +455,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } return START_STICKY; case Intent.ACTION_SHUTDOWN: - logoutAndSave(); + logoutAndSave(true); return START_NOT_STICKY; case ACTION_CLEAR_NOTIFICATION: mNotificationService.clear(); @@ -738,12 +738,16 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa public void onTaskRemoved(final Intent rootIntent) { super.onTaskRemoved(rootIntent); if (!ConversationsPlusPreferences.keepForegroundService()) { - this.logoutAndSave(); + this.logoutAndSave(false); } } - private void logoutAndSave() { + private void logoutAndSave(boolean stop) { + int activeAccounts = 0; for (final Account account : accounts) { + if (account.getStatus() != Account.State.DISABLED) { + activeAccounts++; + } databaseBackend.writeRoster(account.getRoster()); if (account.getXmppConnection() != null) { new Thread(new Runnable() { @@ -752,12 +756,13 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa disconnect(account, false); } }).start(); - cancelWakeUpCall(account.getUuid().hashCode()); } } + if (stop || activeAccounts == 0) { Logging.d(Config.LOGTAG, "good bye"); stopSelf(); } + } private void cancelWakeUpCall(int requestCode) { final AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE); @@ -789,6 +794,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa connection.setOnBindListener(this.mOnBindListener); connection.setOnMessageAcknowledgeListener(this.mOnMessageAcknowledgedListener); connection.addOnAdvancedStreamFeaturesAvailableListener(this.mMessageArchiveService); + connection.addOnAdvancedStreamFeaturesAvailableListener(AvatarService.getInstance()); AxolotlService axolotlService = account.getAxolotlService(); if (axolotlService != null) { connection.addOnAdvancedStreamFeaturesAvailableListener(axolotlService); @@ -2331,12 +2337,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa if (!account.isOptionSet(Account.OPTION_DISABLED)) { if (!force) { disconnect(account, false); - try { - Logging.d(Config.LOGTAG, "wait for disconnect"); - Thread.sleep(500); //sleep wait for disconnect - } catch (InterruptedException e) { - //ignored - } } Thread thread = new Thread(connection); connection.setInteractive(interactive); @@ -2347,6 +2347,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa disconnect(account, force); account.getRoster().clearPresences(); connection.resetEverything(); + account.getAxolotlService().resetBrokenness(); } } } diff --git a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java index acddef2e..eac2c1b8 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConferenceDetailsActivity.java @@ -3,8 +3,10 @@ package eu.siacs.conversations.ui; import android.annotation.TargetApi; import android.app.AlertDialog; import android.app.PendingIntent; +import android.content.ActivityNotFoundException; import android.content.Context; import android.content.DialogInterface; +import android.content.Intent; import android.content.IntentSender.SendIntentException; import android.os.Build; import android.os.Bundle; @@ -284,6 +286,9 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers quickEdit(mConversation.getName(),this.onSubjectEdited); } break; + case R.id.action_share: + share(); + break; case R.id.action_save_as_bookmark: saveAsBookmark(); break; @@ -311,6 +316,18 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers } } + private void share() { + Intent shareIntent = new Intent(); + shareIntent.setAction(Intent.ACTION_SEND); + shareIntent.putExtra(Intent.EXTRA_TEXT, getShareableUri()); + shareIntent.setType("text/plain"); + try { + startActivity(Intent.createChooser(shareIntent, getText(R.string.share_uri_with))); + } catch (ActivityNotFoundException e) { + Toast.makeText(this, R.string.no_application_to_share_uri, Toast.LENGTH_SHORT).show(); + } + } + @Override public boolean onPrepareOptionsMenu(Menu menu) { MenuItem menuItemSaveBookmark = menu.findItem(R.id.action_save_as_bookmark); diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java index cee0021b..74c0e314 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationActivity.java @@ -422,6 +422,7 @@ public class ConversationActivity extends XmppActivity menuInviteContact.setVisible(getSelectedConversation().getMucOptions().canInvite()); menuSecure.setVisible((Config.supportOpenPgp() || Config.supportOmemo()) && Config.multipleEncryptionChoices()); //only if pgp is supported we have a choice } else { + menuContactDetails.setVisible(!this.getSelectedConversation().withSelf()); menuMucDetails.setVisible(false); menuSecure.setVisible(Config.multipleEncryptionChoices()); } @@ -1311,10 +1312,6 @@ public class ConversationActivity extends XmppActivity } else if (requestCode == ATTACHMENT_CHOICE_CHOOSE_FILE || requestCode == ATTACHMENT_CHOICE_RECORD_VOICE) { final List<Uri> uris = extractUriFromIntent(data); final Conversation c = getSelectedConversation(); - final long max = c.getAccount() - .getXmppConnection() - .getFeatures() - .getMaxHttpUploadSize(); final OnPresenceSelected callback = new OnPresenceSelected() { @Override public void onPresenceSelected() { @@ -1328,7 +1325,7 @@ public class ConversationActivity extends XmppActivity } }; if (c.getMode() == Conversation.MODE_MULTI - || FileUtils.allFilesUnderSize(this, uris, max) + || FileUtils.allFilesUnderSize(this, uris, getMaxHttpUploadSize(c)) || c.getNextEncryption() == Message.ENCRYPTION_OTR) { callback.onPresenceSelected(); } else { @@ -1378,6 +1375,10 @@ public class ConversationActivity extends XmppActivity } } + private long getMaxHttpUploadSize(Conversation conversation) { + return conversation.getAccount().getXmppConnection().getFeatures().getMaxHttpUploadSize(); + } + private void openBatteryOptimizationDialogIfNeeded() { if (hasAccountWithoutPush() && isOptimizingBattery() diff --git a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java index 6d911629..d436e60d 100644 --- a/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java +++ b/src/main/java/eu/siacs/conversations/ui/ConversationFragment.java @@ -472,8 +472,10 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa highlightInConference(user); } } else { + if (!message.getContact().isSelf()) { activity.switchToContactDetails(message.getContact(), message.getFingerprint()); } + } } else { Account account = message.getConversation().getAccount(); Intent intent = new Intent(activity, EditAccountActivity.class); diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java index e06e6bd8..d63516bc 100644 --- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java @@ -248,12 +248,11 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate mFetchingAvatar = true; AvatarService.getInstance().checkForAvatar(mAccount, mAvatarFetchCallback); } - } else { - updateSaveButton(); } if (mAccount != null) { updateAccountInformation(false); } + updateSaveButton(); } @Override @@ -532,6 +531,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate protected void onBackendConnected() { if (this.jidToEdit != null) { this.mAccount = xmppConnectionService.findAccountByJid(jidToEdit); + this.mInitMode |= this.mAccount.isOptionSet(Account.OPTION_REGISTER); if (this.mAccount != null) { if (this.mAccount.getPrivateKeyAlias() != null) { this.mPassword.setHint(R.string.authenticate_with_certificate); @@ -626,6 +626,8 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate if (!mInitMode) { this.mAvatar.setVisibility(View.VISIBLE); this.mAvatar.setImageBitmap(AvatarService.getInstance().get(this.mAccount, getPixel(72))); + } else { + this.mAvatar.setVisibility(View.GONE); } if (this.mAccount.isOptionSet(Account.OPTION_REGISTER)) { this.mRegisterNew.setVisibility(View.VISIBLE); diff --git a/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java b/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java index c54781b3..9ad19522 100644 --- a/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ShareWithActivity.java @@ -37,6 +37,8 @@ import eu.siacs.conversations.xmpp.jid.Jid; public class ShareWithActivity extends XmppActivity implements XmppConnectionService.OnConversationUpdate { + private boolean mReturnToPrevious = false; + @Override public void onConversationUpdate() { refreshUi(); @@ -85,7 +87,7 @@ public class ShareWithActivity extends XmppActivity implements XmppConnectionSer resId = R.string.shared_file_with_x; } replaceToast(getString(resId, message.getConversation().getName())); - if (share.uuid != null) { + if (mReturnToPrevious) { finish(); } else { switchToConversation(message.getConversation()); @@ -186,6 +188,7 @@ public class ShareWithActivity extends XmppActivity implements XmppConnectionSer if (intent == null) { return; } + this.mReturnToPrevious = ConversationsPlusPreferences.returnToPrevious(); final String type = intent.getType(); final String action = intent.getAction(); Log.d(Config.LOGTAG, "action: "+action+ ", type:"+type); @@ -314,9 +317,29 @@ public class ShareWithActivity extends XmppActivity implements XmppConnectionSer selectPresence(conversation, callback); } } else { + if (mReturnToPrevious && this.share.text != null && !this.share.text.isEmpty() ) { + final OnPresenceSelected callback = new OnPresenceSelected() { + @Override + public void onPresenceSelected() { + Message message = new Message(conversation,share.text, conversation.getNextEncryption()); + if (conversation.getNextEncryption() == Message.ENCRYPTION_OTR) { + message.setCounterpart(conversation.getNextCounterpart()); + } + xmppConnectionService.sendMessage(message); + replaceToast(getString(R.string.shared_text_with_x, conversation.getName())); + finish(); + } + }; + if (conversation.getNextEncryption() == Message.ENCRYPTION_OTR) { + selectPresence(conversation, callback); + } else { + callback.onPresenceSelected(); + } + } else { switchToConversation(conversation, this.share.text, true); finish(); } + } } diff --git a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java index f6070c32..a45cce01 100644 --- a/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java +++ b/src/main/java/eu/siacs/conversations/xmpp/XmppConnection.java @@ -40,6 +40,8 @@ import java.util.Iterator; import java.util.List; import java.util.Map.Entry; import java.util.TreeSet; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.KeyManager; @@ -118,7 +120,9 @@ public class XmppConnection implements Runnable { private long lastConnect = 0; private long lastSessionStarted = 0; private long lastDiscoStarted = 0; - private int mPendingServiceDiscoveries = 0; + private AtomicInteger mPendingServiceDiscoveries = new AtomicInteger(0); + private AtomicBoolean mIsServiceItemsDiscoveryPending = new AtomicBoolean(true); + private boolean mWaitForDisco = true; private final ArrayList<String> mPendingServiceDiscoveriesIds = new ArrayList<>(); private boolean mInteractive = false; private int attempt = 0; @@ -530,7 +534,6 @@ public class XmppConnection implements Runnable { } nextTag = tagReader.readTag(); } - throw new IOException("reached end of stream. last tag was "+nextTag); } private void acknowledgeStanzaUpTo(int serverCount) { @@ -716,6 +719,23 @@ public class XmppConnection implements Runnable { } else if (this.streamFeatures.hasChild("mechanisms") && shouldAuthenticate && (features.encryptionEnabled || Config.ALLOW_NON_TLS_CONNECTIONS)) { + authenticate(); + } else if (this.streamFeatures.hasChild("sm", "urn:xmpp:sm:" + smVersion) && streamId != null) { + if (Config.EXTENDED_SM_LOGGING) { + Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": resuming after stanza #"+stanzasReceived); + } + final ResumePacket resume = new ResumePacket(this.streamId, stanzasReceived, smVersion); + this.tagWriter.writeStanzaAsync(resume); + } else if (needsBinding) { + if (this.streamFeatures.hasChild("bind")) { + sendBindRequest(); + } else { + throw new IncompatibleServerException(); + } + } + } + + private void authenticate() throws IOException { final List<String> mechanisms = extractMechanisms(streamFeatures .findChild("mechanisms")); final Element auth = new Element("auth"); @@ -752,19 +772,6 @@ public class XmppConnection implements Runnable { } else { throw new IncompatibleServerException(); } - } else if (this.streamFeatures.hasChild("sm", "urn:xmpp:sm:" + smVersion) && streamId != null) { - if (Config.EXTENDED_SM_LOGGING) { - Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": resuming after stanza #"+stanzasReceived); - } - final ResumePacket resume = new ResumePacket(this.streamId, stanzasReceived, smVersion); - this.tagWriter.writeStanzaAsync(resume); - } else if (needsBinding) { - if (this.streamFeatures.hasChild("bind")) { - sendBindRequest(); - } else { - throw new IncompatibleServerException(); - } - } } private List<String> extractMechanisms(final Element stream) { @@ -984,11 +991,12 @@ public class XmppConnection implements Runnable { synchronized (this.disco) { this.disco.clear(); } - mPendingServiceDiscoveries = mServerIdentity == Identity.NIMBUZZ ? 1 : 0; + mPendingServiceDiscoveries.set(0); + mIsServiceItemsDiscoveryPending.set(true); + mWaitForDisco = mServerIdentity != Identity.NIMBUZZ; lastDiscoStarted = SystemClock.elapsedRealtime(); Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": starting service discovery"); mXmppConnectionService.scheduleWakeUpCall(Config.CONNECT_DISCO_TIMEOUT, account.getUuid().hashCode()); - sendServiceDiscoveryItems(account.getServer()); Element caps = streamFeatures.findChild("c"); final String hash = caps == null ? null : caps.getAttribute("hash"); final String ver = caps == null ? null : caps.getAttribute("ver"); @@ -1003,13 +1011,15 @@ public class XmppConnection implements Runnable { disco.put(account.getServer(), discoveryResult); } sendServiceDiscoveryInfo(account.getJid().toBareJid()); + sendServiceDiscoveryItems(account.getServer()); + if (!mWaitForDisco) { + finalizeBind(); + } this.lastSessionStarted = SystemClock.elapsedRealtime(); } private void sendServiceDiscoveryInfo(final Jid jid) { - if (mServerIdentity != Identity.NIMBUZZ) { - mPendingServiceDiscoveries++; - } + mPendingServiceDiscoveries.incrementAndGet(); final IqPacket iq = new IqPacket(IqPacket.TYPE.GET); iq.setTo(jid); iq.query("http://jabber.org/protocol/disco#info"); @@ -1053,14 +1063,10 @@ public class XmppConnection implements Runnable { Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not query disco info for " + jid.toString()); } if (packet.getType() != IqPacket.TYPE.TIMEOUT) { - mPendingServiceDiscoveries--; - if (mPendingServiceDiscoveries == 0) { - Logging.d(Config.LOGTAG,account.getJid().toBareJid()+": done with service discovery"); - Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": online with resource " + account.getResource()); - if (bindListener != null) { - bindListener.onBind(account); - } - changeStatus(Account.State.ONLINE); + if (mPendingServiceDiscoveries.decrementAndGet() == 0 + && !mIsServiceItemsDiscoveryPending.get() + && mWaitForDisco) { + finalizeBind(); } } } @@ -1070,6 +1076,14 @@ public class XmppConnection implements Runnable { } } + private void finalizeBind() { + Logging.d(Config.LOGTAG, account.getJid().toBareJid() + ": online with resource " + account.getResource()); + if (bindListener != null) { + bindListener.onBind(account); + } + changeStatus(Account.State.ONLINE); + } + private void enableAdvancedStreamFeatures() { if (getFeatures().carbons() && !features.carbonsEnabled) { sendEnableCarbons(); @@ -1087,7 +1101,7 @@ public class XmppConnection implements Runnable { final IqPacket iq = new IqPacket(IqPacket.TYPE.GET); iq.setTo(server.toDomainJid()); iq.query("http://jabber.org/protocol/disco#items"); - this.sendIqPacket(iq, new OnIqPacketReceived() { + String id = this.sendIqPacket(iq, new OnIqPacketReceived() { @Override public void onIqPacketReceived(final Account account, final IqPacket packet) { @@ -1104,8 +1118,17 @@ public class XmppConnection implements Runnable { } else { Log.d(Config.LOGTAG, account.getJid().toBareJid() + ": could not query disco items of " + server); } + if (packet.getType() != IqPacket.TYPE.TIMEOUT) { + mIsServiceItemsDiscoveryPending.set(false); + if (mPendingServiceDiscoveries.get() == 0 && mWaitForDisco) { + finalizeBind(); + } + } } }); + synchronized (this.mPendingServiceDiscoveriesIds) { + this.mPendingServiceDiscoveriesIds.add(id); + } } private void sendEnableCarbons() { @@ -1486,14 +1509,16 @@ public class XmppConnection implements Runnable { public boolean pep() { synchronized (XmppConnection.this.disco) { - ServiceDiscoveryResult info = disco.get(account.getServer()); - if (info != null && info.hasIdentity("pubsub", "pep")) { - return true; - } else { - info = disco.get(account.getJid().toBareJid()); + ServiceDiscoveryResult info = disco.get(account.getJid().toBareJid()); return info != null && info.hasIdentity("pubsub", "pep"); } } + + public boolean pepPersistent() { + synchronized (XmppConnection.this.disco) { + ServiceDiscoveryResult info = disco.get(account.getJid().toBareJid()); + return info != null && info.getFeatures().contains("http://jabber.org/protocol/pubsub#persistent-items"); + } } public boolean mam() { |