diff options
Diffstat (limited to 'src/main/java/de/thedevstack')
28 files changed, 645 insertions, 338 deletions
diff --git a/src/main/java/de/thedevstack/conversationsplus/Config.java b/src/main/java/de/thedevstack/conversationsplus/Config.java index 2f35ad6e..a6825246 100644 --- a/src/main/java/de/thedevstack/conversationsplus/Config.java +++ b/src/main/java/de/thedevstack/conversationsplus/Config.java @@ -34,77 +34,62 @@ public final class Config { return (ENCRYPTION_MASK & (ENCRYPTION_MASK - 1)) != 0; } - public static final String LOGTAG = "conversations"; + public static final String LOGTAG = BuildConfig.LOGTAG; + public static final String DOMAIN_LOCK = BuildConfig.LOCKED_IN_DOMAIN; //only allow account creation for this domain + public static final String CONFERENCE_DOMAIN_LOCK = BuildConfig.LOCKED_IN_DOMAIN_CONFERENCES; //only allow conference creation for this domain + public static final boolean LOCK_DOMAINS_IN_CONVERSATIONS = BuildConfig.CONTACTS_CONFERENCES_LOCKED_TO_DOMAIN; //only add contacts and conferences for own domains - public static final String DOMAIN_LOCK = null; //only allow account creation for this domain - public static final String CONFERENCE_DOMAIN_LOCK = null; //only allow conference creation for this domain - public static final boolean LOCK_DOMAINS_IN_CONVERSATIONS = false; //only add contacts and conferences for own domains + public static final boolean LOCK_SETTINGS = BuildConfig.ACCOUNT_SETTINGS_LOCKED; //set to true to disallow account and settings editing + public static final boolean DISALLOW_REGISTRATION_IN_UI = BuildConfig.DISALLOW_REGISTRATION_IN_UI; //hide the register checkbox - public static final boolean LOCK_SETTINGS = false; //set to true to disallow account and settings editing - public static final boolean DISALLOW_REGISTRATION_IN_UI = false; //hide the register checkbox + public static final boolean ALLOW_NON_TLS_CONNECTIONS = BuildConfig.ALLOW_NON_TLS_CONNECTIONS; //very dangerous. you should have a good reason to set this to true + public static final boolean HIDE_MESSAGE_TEXT_IN_NOTIFICATION = BuildConfig.HIDE_MESSAGE_TEXT_IN_NOTIFICATION; + public static final boolean SHOW_CONNECTED_ACCOUNTS = BuildConfig.SHOW_CONNECTED_ACCOUNTS_IN_FOREGROUND_NOTIFICATION; //show number of connected accounts in foreground notification - 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 int PING_MAX_INTERVAL = BuildConfig.PING_MAX_INTERVAL; + public static final int PING_MIN_INTERVAL = BuildConfig.PING_MIN_INTERVAL; + public static final int PING_TIMEOUT = BuildConfig.PING_TIMEOUT; + public static final int SOCKET_TIMEOUT = BuildConfig.SOCKET_TIMEOUT; + public static final int CONNECT_TIMEOUT = BuildConfig.CONNECT_TIMEOUT; + public static final int CONNECT_DISCO_TIMEOUT = BuildConfig.CONNECT_DISCO_TIMEOUT; + public static final int CARBON_GRACE_PERIOD = BuildConfig.CARBON_GRACE_PERIOD; + public static final int MINI_GRACE_PERIOD = BuildConfig.MINI_GRACE_PERIOD; - public static final boolean ALWAYS_NOTIFY_BY_DEFAULT = false; + public static final boolean CLOSE_TCP_WHEN_SWITCHING_TO_BACKGROUND = BuildConfig.CLOSE_TCP_WHEN_SWITCHING_TO_BACKGROUND; - public static final boolean LEGACY_NAMESPACE_HTTP_UPLOAD = false; + public static final int AVATAR_SIZE = BuildConfig.AVATAR_SIZE; + public static final Bitmap.CompressFormat AVATAR_FORMAT = BuildConfig.AVATAR_FORMAT; - public static final int PING_MAX_INTERVAL = 300; - public static final int PING_MIN_INTERVAL = 30; - public static final int PING_TIMEOUT = 15; - public static final int SOCKET_TIMEOUT = 15; - public static final int CONNECT_TIMEOUT = 90; - public static final int CONNECT_DISCO_TIMEOUT = 20; - public static final int CARBON_GRACE_PERIOD = 90; - public static final int MINI_GRACE_PERIOD = 750; + public static final int PAGE_SIZE = BuildConfig.PAGE_SIZE; + public static final int MAX_NUM_PAGES = BuildConfig.MAX_NUM_PAGES; - public static final boolean CLOSE_TCP_WHEN_SWITCHING_TO_BACKGROUND = false; + public static final int REFRESH_UI_INTERVAL = BuildConfig.REFRESH_UI_INTERVAL; - public static final int AVATAR_SIZE = 192; - public static final Bitmap.CompressFormat AVATAR_FORMAT = Bitmap.CompressFormat.PNG; + public static final boolean DISABLE_PROXY_LOOKUP = BuildConfig.DISABLE_PROXY_LOOKUP; //useful to debug ibb + public static final boolean DISABLE_HTTP_UPLOAD = BuildConfig.DISABLE_HTTP_UPLOAD; + public static final boolean DISABLE_STRING_PREP = BuildConfig.DISABLE_STRING_PREP; // setting to true might increase startup performance + public static final boolean EXTENDED_SM_LOGGING = BuildConfig.EXTENDED_SM_LOGGING; // log stanza counts + public static final boolean RESET_ATTEMPT_COUNT_ON_NETWORK_CHANGE = BuildConfig.RESET_ATTEMPT_COUNT_ON_NETWORK_CHANGE; //setting to true might increase power consumption - public static final int IMAGE_SIZE = 1920; - public static final Bitmap.CompressFormat IMAGE_FORMAT = Bitmap.CompressFormat.JPEG; - public static final int IMAGE_QUALITY = 75; - public static final int IMAGE_MAX_SIZE = 524288; //512KiB + public static final boolean ENCRYPT_ON_HTTP_UPLOADED = BuildConfig.ENCRYPT_ON_HTTP_UPLOADED; - public static final int MESSAGE_MERGE_WINDOW = 20; + public static final boolean REPORT_WRONG_FILESIZE_IN_OTR_JINGLE = BuildConfig.REPORT_WRONG_FILESIZE_IN_OTR_JINGLE; - public static final boolean UTF8_EMOTICONS = false; + public static final boolean SHOW_REGENERATE_AXOLOTL_KEYS_BUTTON = BuildConfig.SHOW_REGENERATE_AXOLOTL_KEYS_BUTTON; - public static final int PAGE_SIZE = 50; - public static final int MAX_NUM_PAGES = 3; + public static final boolean X509_VERIFICATION = BuildConfig.X509_VERIFICATION_OF_OMEMO_KEYS; //use x509 certificates to verify OMEMO keys - public static final int PROGRESS_UI_UPDATE_INTERVAL = 750; - public static final int REFRESH_UI_INTERVAL = 500; + public static final boolean IGNORE_ID_REWRITE_IN_MUC = BuildConfig.IGNORE_ID_REWRITE_IN_MUC; - public static final boolean DISABLE_PROXY_LOOKUP = false; //useful to debug ibb - public static final boolean DISABLE_HTTP_UPLOAD = false; - 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 boolean RESET_ATTEMPT_COUNT_ON_NETWORK_CHANGE = true; //setting to true might increase power consumption - - public static final boolean ENCRYPT_ON_HTTP_UPLOADED = false; - - public static final boolean REPORT_WRONG_FILESIZE_IN_OTR_JINGLE = true; - - public static final boolean SHOW_REGENERATE_AXOLOTL_KEYS_BUTTON = false; - - public static final boolean X509_VERIFICATION = false; //use x509 certificates to verify OMEMO keys - - public static final boolean IGNORE_ID_REWRITE_IN_MUC = true; - - public static final boolean REQUEST_DISCO = true; + public static final boolean REQUEST_DISCO = BuildConfig.REQUEST_DISCO; 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 int MAM_MAX_MESSAGES = 500; + public static final int MAM_MAX_MESSAGES = BuildConfig.MAM_MAX_MESSAGES; public static final ChatState DEFAULT_CHATSTATE = ChatState.ACTIVE; - public static final int TYPING_TIMEOUT = 8; + public static final int TYPING_TIMEOUT = BuildConfig.TYPING_TIMEOUT; public static final String ENABLED_CIPHERS[] = { "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", diff --git a/src/main/java/de/thedevstack/conversationsplus/ConversationsPlusPreferences.java b/src/main/java/de/thedevstack/conversationsplus/ConversationsPlusPreferences.java index dda4208c..70eec7a2 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ConversationsPlusPreferences.java +++ b/src/main/java/de/thedevstack/conversationsplus/ConversationsPlusPreferences.java @@ -81,6 +81,49 @@ public class ConversationsPlusPreferences extends Settings { return getBoolean("enable_legacy_ssl", false); } + public static boolean showBatteryOptimization() { + return getBoolean("show_battery_optimization", true); + } + + public static void commitShowBatteryOptimization(boolean showBatteryOptimization) { + commitBoolean("show_battery_optimization", showBatteryOptimization); + } + + public static boolean advancedMucMode() { + return getBoolean("advanced_muc_mode", false); + } + + public static void commitAdvancedMucMode(boolean advancedMucMode) { + commitBoolean("advanced_muc_mode", advancedMucMode); + } + + /** + * Whether to show extended connection options or not + * @return + */ + public static boolean showConnectionOptions() { + return getBoolean("show_connection_options", false); + } + + /** + * Whether to respect auto join on bookmarks or not. + * @return + */ + public static boolean autojoin() { + return getBoolean("autojoin", true); + } + public static boolean xaOnSilentMode() { + return getBoolean("xa_on_silent_mode", false); + } + + public static boolean treatVibrateAsSilent() { + return getBoolean("treat_vibrate_as_silent", false); + } + + public static boolean awayWhenScreenOff() { + return getBoolean("away_when_screen_off", false); + } + public static boolean useSubject() { return getBoolean("use_subject", true); } @@ -194,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/crypto/axolotl/AxolotlService.java b/src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/AxolotlService.java index 49cd6033..52418553 100644 --- a/src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/AxolotlService.java +++ b/src/main/java/de/thedevstack/conversationsplus/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/de/thedevstack/conversationsplus/crypto/axolotl/AxolotlServiceImpl.java b/src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/AxolotlServiceImpl.java index 2463a795..f8856f90 100644 --- a/src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/AxolotlServiceImpl.java +++ b/src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/AxolotlServiceImpl.java @@ -43,12 +43,13 @@ import de.thedevstack.conversationsplus.services.XmppConnectionService; import de.thedevstack.conversationsplus.utils.CryptoHelper; import de.thedevstack.conversationsplus.utils.SerialSingleThreadExecutor; import de.thedevstack.conversationsplus.xml.Element; +import de.thedevstack.conversationsplus.xmpp.OnAdvancedStreamFeaturesLoaded; import de.thedevstack.conversationsplus.xmpp.OnIqPacketReceived; import de.thedevstack.conversationsplus.xmpp.jid.InvalidJidException; import de.thedevstack.conversationsplus.xmpp.jid.Jid; import de.thedevstack.conversationsplus.xmpp.stanzas.IqPacket; -public class AxolotlServiceImpl implements AxolotlService { +public class AxolotlServiceImpl implements OnAdvancedStreamFeaturesLoaded, AxolotlService { public static final int publishTriesThreshold = 3; @@ -223,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); } @@ -302,6 +304,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(); @@ -436,7 +443,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")); } } @@ -600,7 +608,8 @@ 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")); } } @@ -1037,4 +1046,4 @@ public class AxolotlServiceImpl implements AxolotlService { } } } -}
\ No newline at end of file +} diff --git a/src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/AxolotlServiceStub.java b/src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/AxolotlServiceStub.java index 858cdf08..7db99b75 100644 --- a/src/main/java/de/thedevstack/conversationsplus/crypto/axolotl/AxolotlServiceStub.java +++ b/src/main/java/de/thedevstack/conversationsplus/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/de/thedevstack/conversationsplus/entities/Conversation.java b/src/main/java/de/thedevstack/conversationsplus/entities/Conversation.java index 50b1e4a7..9e9366d7 100644 --- a/src/main/java/de/thedevstack/conversationsplus/entities/Conversation.java +++ b/src/main/java/de/thedevstack/conversationsplus/entities/Conversation.java @@ -317,14 +317,6 @@ public class Conversation extends AbstractEntity implements Blockable { setAttribute(ATTRIBUTE_CRYPTO_TARGETS, acceptedTargets); } - public void setCorrectingMessage(Message correctingMessage) { - this.correctingMessage = correctingMessage; - } - - public Message getCorrectingMessage() { - return this.correctingMessage; - } - public interface OnMessageFound { void onMessageFound(final Message message); } @@ -790,7 +782,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/de/thedevstack/conversationsplus/entities/MucOptions.java b/src/main/java/de/thedevstack/conversationsplus/entities/MucOptions.java index 4a330f30..1af538ec 100644 --- a/src/main/java/de/thedevstack/conversationsplus/entities/MucOptions.java +++ b/src/main/java/de/thedevstack/conversationsplus/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/de/thedevstack/conversationsplus/http/HttpUploadConnection.java b/src/main/java/de/thedevstack/conversationsplus/http/HttpUploadConnection.java index f28dee36..6c546fee 100644 --- a/src/main/java/de/thedevstack/conversationsplus/http/HttpUploadConnection.java +++ b/src/main/java/de/thedevstack/conversationsplus/http/HttpUploadConnection.java @@ -220,8 +220,12 @@ public class HttpUploadConnection implements Transferable { fail(); } } catch (IOException e) { - errorStream = connection.getErrorStream(); - Logging.e("httpupload", "http response: " + new Scanner(errorStream).useDelimiter("\\A").next() + ", exception message: " + e.getMessage()); + errorStream = (null != connection) ? connection.getErrorStream() : null; + String httpResponseMessage = null; + if (null != errorStream) { + httpResponseMessage = new Scanner(errorStream).useDelimiter("\\A").next(); + } + Logging.e("httpupload", ((null != httpResponseMessage) ? ("http response: " + httpResponseMessage + ", ") : "") + "exception message: " + e.getMessage()); fail(); } finally { StreamUtil.close(os); diff --git a/src/main/java/de/thedevstack/conversationsplus/parser/MessageParser.java b/src/main/java/de/thedevstack/conversationsplus/parser/MessageParser.java index c65df887..6fb18266 100644 --- a/src/main/java/de/thedevstack/conversationsplus/parser/MessageParser.java +++ b/src/main/java/de/thedevstack/conversationsplus/parser/MessageParser.java @@ -415,7 +415,7 @@ public class MessageParser extends AbstractParser implements || (isTypeGroupChat && packet.hasChild("delay","urn:xmpp:delay")) || message.getType() == Message.TYPE_PRIVATE; if (checkForDuplicates && conversation.hasDuplicateMessage(message)) { - Log.d(Config.LOGTAG,"skipping duplicate message from "+message.getCounterpart().toString()+" "+message.getBody()); + Logging.d(Config.LOGTAG, "skipping duplicate message from '" + message.getCounterpart().toString() + "' with remote id " + message.getRemoteMsgId()); return; } diff --git a/src/main/java/de/thedevstack/conversationsplus/services/AvatarService.java b/src/main/java/de/thedevstack/conversationsplus/services/AvatarService.java index 6311d739..ed9c259f 100644 --- a/src/main/java/de/thedevstack/conversationsplus/services/AvatarService.java +++ b/src/main/java/de/thedevstack/conversationsplus/services/AvatarService.java @@ -31,14 +31,17 @@ import de.thedevstack.conversationsplus.entities.Message; import de.thedevstack.conversationsplus.entities.MucOptions; import de.thedevstack.conversationsplus.generator.IqGenerator; import de.thedevstack.conversationsplus.persistance.DatabaseBackend; +import de.thedevstack.conversationsplus.persistance.FileBackend; import de.thedevstack.conversationsplus.ui.UiCallback; import de.thedevstack.conversationsplus.utils.UIHelper; import de.thedevstack.conversationsplus.xml.Element; +import de.thedevstack.conversationsplus.xmpp.OnAdvancedStreamFeaturesLoaded; import de.thedevstack.conversationsplus.xmpp.OnIqPacketReceived; +import de.thedevstack.conversationsplus.xmpp.XmppConnection; import de.thedevstack.conversationsplus.xmpp.pep.Avatar; import de.thedevstack.conversationsplus.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; } - 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 { + 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) { + 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_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/de/thedevstack/conversationsplus/services/ContactChooserTargetService.java b/src/main/java/de/thedevstack/conversationsplus/services/ContactChooserTargetService.java index 8d48d686..6256609c 100644 --- a/src/main/java/de/thedevstack/conversationsplus/services/ContactChooserTargetService.java +++ b/src/main/java/de/thedevstack/conversationsplus/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/de/thedevstack/conversationsplus/services/MessageArchiveService.java b/src/main/java/de/thedevstack/conversationsplus/services/MessageArchiveService.java index f2298d8c..cd55b42f 100644 --- a/src/main/java/de/thedevstack/conversationsplus/services/MessageArchiveService.java +++ b/src/main/java/de/thedevstack/conversationsplus/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, @@ -292,7 +292,7 @@ public class MessageArchiveService implements OnAdvancedStreamFeaturesLoaded { this.end = end; this.queryId = new BigInteger(50, mXmppConnectionService.getRNG()).toString(32); } - + private Query page(String reference) { Query query = new Query(this.account,this.start,this.end); query.reference = reference; diff --git a/src/main/java/de/thedevstack/conversationsplus/services/NotificationService.java b/src/main/java/de/thedevstack/conversationsplus/services/NotificationService.java index 1baec3a5..a3142df4 100644 --- a/src/main/java/de/thedevstack/conversationsplus/services/NotificationService.java +++ b/src/main/java/de/thedevstack/conversationsplus/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()); - mBuilder.setLights(Settings.LED_COLOR, 2000, 4000); + if (led) { + mBuilder.setLights(Settings.LED_COLOR, 2000, 4000); + } final Notification notification = mBuilder.build(); notificationManager.notify(NOTIFICATION_ID, notification); } @@ -537,7 +540,7 @@ public class NotificationService { errors.add(account); } } - if (mXmppConnectionService.getPreferences().getBoolean("keep_foreground_service", false)) { + if (ConversationsPlusPreferences.keepForegroundService()) { notificationManager.notify(FOREGROUND_NOTIFICATION_ID, createForegroundNotification()); } final NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(mXmppConnectionService); diff --git a/src/main/java/de/thedevstack/conversationsplus/services/XmppConnectionService.java b/src/main/java/de/thedevstack/conversationsplus/services/XmppConnectionService.java index d92401ba..c1426080 100644 --- a/src/main/java/de/thedevstack/conversationsplus/services/XmppConnectionService.java +++ b/src/main/java/de/thedevstack/conversationsplus/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(); @@ -481,13 +481,13 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } break; case AudioManager.RINGER_MODE_CHANGED_ACTION: - if (xaOnSilentMode()) { + if (ConversationsPlusPreferences.xaOnSilentMode()) { refreshAllPresences(); } break; case Intent.ACTION_SCREEN_OFF: case Intent.ACTION_SCREEN_ON: - if (awayWhenScreenOff()) { + if (ConversationsPlusPreferences.awayWhenScreenOff()) { refreshAllPresences(); } break; @@ -572,22 +572,10 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa return START_STICKY; } - private boolean xaOnSilentMode() { - return getPreferences().getBoolean("xa_on_silent_mode", false); - } - - private boolean treatVibrateAsSilent() { - return getPreferences().getBoolean("treat_vibrate_as_silent", false); - } - - private boolean awayWhenScreenOff() { - return getPreferences().getBoolean("away_when_screen_off", false); - } - private Presence.Status getTargetPresence() { - if (xaOnSilentMode() && isPhoneSilenced()) { + if (ConversationsPlusPreferences.xaOnSilentMode() && isPhoneSilenced()) { return Presence.Status.XA; - } else if (awayWhenScreenOff() && !isInteractive()) { + } else if (ConversationsPlusPreferences.awayWhenScreenOff() && !isInteractive()) { return Presence.Status.AWAY; } else { return Presence.Status.ONLINE; @@ -610,7 +598,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa private boolean isPhoneSilenced() { AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE); - if (treatVibrateAsSilent()) { + if (ConversationsPlusPreferences.treatVibrateAsSilent()) { return audioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL; } else { return audioManager.getRingerMode() == AudioManager.RINGER_MODE_SILENT; @@ -725,7 +713,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } public void toggleScreenEventReceiver() { - if (awayWhenScreenOff()) { + if (ConversationsPlusPreferences.awayWhenScreenOff()) { final IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON); filter.addAction(Intent.ACTION_SCREEN_OFF); registerReceiver(this.mEventReceiver, filter); @@ -750,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() { @@ -764,11 +756,12 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa disconnect(account, false); } }).start(); - cancelWakeUpCall(account.getUuid().hashCode()); } } - Logging.d(Config.LOGTAG, "good bye"); - stopSelf(); + if (stop || activeAccounts == 0) { + Logging.d(Config.LOGTAG, "good bye"); + stopSelf(); + } } private void cancelWakeUpCall(int requestCode) { @@ -801,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); @@ -967,7 +961,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa if (addToConversation) { conversation.add(message); } - if (message.getEncryption() == Message.ENCRYPTION_NONE || saveEncryptedMessages()) { + if (message.getEncryption() == Message.ENCRYPTION_NONE || !ConversationsPlusPreferences.dontSaveEncrypted()) { if (saveInDb) { databaseBackend.createMessage(message); } else if (message.edited()) { @@ -992,11 +986,11 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa private void sendUnsentMessages(final Conversation conversation) { conversation.findWaitingMessages(new Conversation.OnMessageFound() { - @Override - public void onMessageFound(Message message) { - resendMessage(message, true); - } - }); + @Override + public void onMessageFound(Message message) { + resendMessage(message, true); + } + }); } public void resendMessage(final Message message, final boolean delay) { @@ -1027,7 +1021,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa final Element query = packet.query(); final HashMap<Jid, Bookmark> bookmarks = new HashMap<>(); final Element storage = query.findChild("storage", "storage:bookmarks"); - final boolean autojoin = respectAutojoin(); if (storage != null) { for (final Element item : storage.getChildren()) { if (item.getName().equals("conference")) { @@ -1039,7 +1032,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa Conversation conversation = find(bookmark); if (conversation != null) { conversation.setBookmark(bookmark); - } else if (bookmark.autojoin() && bookmark.getJid() != null && autojoin) { + } else if (bookmark.autojoin() && bookmark.getJid() != null && ConversationsPlusPreferences.autojoin()) { conversation = findOrCreateConversation( account, bookmark.getJid(), true); conversation.setBookmark(bookmark); @@ -1219,19 +1212,19 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa } } Collections.sort(list, new Comparator<Conversation>() { - @Override - public int compare(Conversation lhs, Conversation rhs) { - Message left = lhs.getLatestMessage(); - Message right = rhs.getLatestMessage(); - if (left.getTimeSent() > right.getTimeSent()) { - return -1; - } else if (left.getTimeSent() < right.getTimeSent()) { - return 1; - } else { - return 0; - } - } - }); + @Override + public int compare(Conversation lhs, Conversation rhs) { + Message left = lhs.getLatestMessage(); + Message right = rhs.getLatestMessage(); + if (left.getTimeSent() > right.getTimeSent()) { + return -1; + } else if (left.getTimeSent() < right.getTimeSent()) { + return 1; + } else { + return 0; + } + } + }); } public void loadMoreMessages(final Conversation conversation, final long timestamp, final OnMoreMessagesLoaded callback) { @@ -1380,7 +1373,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa if (conversation.getMode() == Conversation.MODE_MULTI) { if (conversation.getAccount().getStatus() == Account.State.ONLINE) { Bookmark bookmark = conversation.getBookmark(); - if (bookmark != null && bookmark.autojoin() && respectAutojoin()) { + if (bookmark != null && bookmark.autojoin() && ConversationsPlusPreferences.autojoin()) { bookmark.setAutojoin(false); pushBookmarks(bookmark.getAccount()); } @@ -1886,9 +1879,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa if (conversation.getMode() == Conversation.MODE_MULTI) { conversation.getMucOptions().setPassword(password); if (conversation.getBookmark() != null) { - if (respectAutojoin()) { - conversation.getBookmark().setAutojoin(true); - } + conversation.getBookmark().setAutojoin(ConversationsPlusPreferences.autojoin()); pushBookmarks(conversation.getAccount()); } databaseBackend.updateConversation(conversation); @@ -2240,9 +2231,9 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa final Account account = conversation.getAccount(); final Session otrSession = conversation.getOtrSession(); Logging.d(Config.LOGTAG, - account.getJid().toBareJid() + " otr session established with " - + conversation.getJid() + "/" - + otrSession.getSessionID().getUserID()); + account.getJid().toBareJid() + " otr session established with " + + conversation.getJid() + "/" + + otrSession.getSessionID().getUserID()); conversation.findUnsentMessagesWithEncryption(Message.ENCRYPTION_OTR, new Conversation.OnMessageFound() { @Override @@ -2280,7 +2271,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa packet.setFrom(account.getJid()); MessageGenerator.addMessageHints(packet); packet.setAttribute("to", otrSession.getSessionID().getAccountID() + "/" - + otrSession.getSessionID().getUserID()); + + otrSession.getSessionID().getUserID()); try { packet.setBody(otrSession .transformSending(CryptoHelper.FILETRANSFER @@ -2346,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); @@ -2362,6 +2347,7 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa disconnect(account, force); account.getRoster().clearPresences(); connection.resetEverything(); + account.getAxolotlService().resetBrokenness(); } } } @@ -2441,23 +2427,6 @@ public class XmppConnectionService extends Service implements OnPhoneContactsLoa updateConversationUi(); } - public SharedPreferences getPreferences() { - return PreferenceManager - .getDefaultSharedPreferences(getApplicationContext()); - } - - public boolean saveEncryptedMessages() { - return !getPreferences().getBoolean("dont_save_encrypted", false); - } - - private boolean respectAutojoin() { - return getPreferences().getBoolean("autojoin", true); - } - - public boolean showExtendedConnectionOptions() { - return getPreferences().getBoolean("show_connection_options", false); - } - public int unreadCount() { int count = 0; for (Conversation conversation : getConversations()) { diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/ConferenceDetailsActivity.java b/src/main/java/de/thedevstack/conversationsplus/ui/ConferenceDetailsActivity.java index 12ff2a52..2a3cd7fe 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ui/ConferenceDetailsActivity.java +++ b/src/main/java/de/thedevstack/conversationsplus/ui/ConferenceDetailsActivity.java @@ -3,8 +3,10 @@ package de.thedevstack.conversationsplus.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; @@ -30,6 +32,7 @@ import java.util.Comparator; import java.util.concurrent.atomic.AtomicInteger; import de.thedevstack.conversationsplus.Config; +import de.thedevstack.conversationsplus.ConversationsPlusPreferences; import de.thedevstack.conversationsplus.R; import de.thedevstack.conversationsplus.crypto.PgpEngine; import de.thedevstack.conversationsplus.entities.Account; @@ -262,7 +265,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers }); } }); - this.mAdvancedMode = getPreferences().getBoolean("advanced_muc_mode", false); + this.mAdvancedMode = ConversationsPlusPreferences.advancedMucMode(); this.mConferenceInfoTable = (TableLayout) findViewById(R.id.muc_info_more); mConferenceInfoTable.setVisibility(this.mAdvancedMode ? View.VISIBLE : View.GONE); this.mConferenceInfoMam = (TextView) findViewById(R.id.muc_info_mam); @@ -282,6 +285,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; @@ -291,7 +297,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers case R.id.action_advanced_mode: this.mAdvancedMode = !menuItem.isChecked(); menuItem.setChecked(this.mAdvancedMode); - getPreferences().edit().putBoolean("advanced_muc_mode", mAdvancedMode).commit(); + ConversationsPlusPreferences.commitAdvancedMucMode(mAdvancedMode); mConferenceInfoTable.setVisibility(this.mAdvancedMode ? View.VISIBLE : View.GONE); invalidateOptionsMenu(); updateView(); @@ -309,6 +315,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); @@ -468,7 +486,7 @@ public class ConferenceDetailsActivity extends XmppActivity implements OnConvers bookmark.setNick(mConversation.getJid().getResourcepart()); } bookmark.setBookmarkName(mConversation.getMucOptions().getSubject()); - bookmark.setAutojoin(getPreferences().getBoolean("autojoin",true)); + bookmark.setAutojoin(ConversationsPlusPreferences.autojoin()); account.getBookmarks().add(bookmark); xmppConnectionService.pushBookmarks(account); mConversation.setBookmark(bookmark); diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/ConversationActivity.java b/src/main/java/de/thedevstack/conversationsplus/ui/ConversationActivity.java index dbaa5a7c..e46095e7 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ui/ConversationActivity.java +++ b/src/main/java/de/thedevstack/conversationsplus/ui/ConversationActivity.java @@ -113,6 +113,7 @@ public class ConversationActivity extends XmppActivity private Message mPendingDownloadableMessage = null; private boolean conversationWasSelectedByKeyboard = false; + protected boolean mUsingEnterKey = false; private View mContentView; private View avatarLogoView; @@ -190,6 +191,7 @@ public class ConversationActivity extends XmppActivity mPendingImageUris.add(Uri.parse(pending)); } } + this.mUsingEnterKey = ConversationsPlusPreferences.displayEnterKey(); setContentView(R.layout.fragment_conversations_overview); @@ -337,12 +339,12 @@ public class ConversationActivity extends XmppActivity public void switchToConversation(Conversation conversation) { setSelectedConversation(conversation); runOnUiThread(new Runnable() { - @Override - public void run() { - ConversationActivity.this.mConversationFragment.reInit(getSelectedConversation()); - openConversation(); - } - }); + @Override + public void run() { + ConversationActivity.this.mConversationFragment.reInit(getSelectedConversation()); + openConversation(); + } + }); } private void updateActionBarTitle() { @@ -776,19 +778,19 @@ public class ConversationActivity extends XmppActivity builder.setView(dialogView); builder.setNegativeButton(getString(R.string.cancel), null); builder.setPositiveButton(getString(R.string.delete_messages), - new OnClickListener() { - - @Override - public void onClick(DialogInterface dialog, int which) { - ConversationActivity.this.xmppConnectionService.clearConversationHistory(conversation); - if (endConversationCheckBox.isChecked()) { - endConversation(conversation); - } else { - updateConversationList(); - ConversationActivity.this.mConversationFragment.updateMessages(); - } - } - }); + new OnClickListener() { + + @Override + public void onClick(DialogInterface dialog, int which) { + ConversationActivity.this.xmppConnectionService.clearConversationHistory(conversation); + if (endConversationCheckBox.isChecked()) { + endConversation(conversation); + } else { + updateConversationList(); + ConversationActivity.this.mConversationFragment.updateMessages(); + } + } + }); builder.create().show(); } @@ -959,24 +961,24 @@ public class ConversationActivity extends XmppActivity builder.setTitle(R.string.disable_notifications); final int[] durations = getResources().getIntArray(R.array.mute_options_durations); builder.setItems(R.array.mute_options_descriptions, - new OnClickListener() { - - @Override - public void onClick(final DialogInterface dialog, final int which) { - final long till; - if (durations[which] == -1) { - till = Long.MAX_VALUE; - } else { - till = System.currentTimeMillis() + (durations[which] * 1000); - } - conversation.setMutedTill(till); - ConversationActivity.this.xmppConnectionService.databaseBackend - .updateConversation(conversation); - updateConversationList(); - ConversationActivity.this.mConversationFragment.updateMessages(); - invalidateOptionsMenu(); - } - }); + new OnClickListener() { + + @Override + public void onClick(final DialogInterface dialog, final int which) { + final long till; + if (durations[which] == -1) { + till = Long.MAX_VALUE; + } else { + till = System.currentTimeMillis() + (durations[which] * 1000); + } + conversation.setMutedTill(till); + ConversationActivity.this.xmppConnectionService.databaseBackend + .updateConversation(conversation); + updateConversationList(); + ConversationActivity.this.mConversationFragment.updateMessages(); + invalidateOptionsMenu(); + } + }); builder.create().show(); } @@ -1372,10 +1374,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() { @@ -1389,7 +1387,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 { @@ -1433,19 +1431,20 @@ public class ConversationActivity extends XmppActivity mConversationFragment.onActivityResult(requestCode, resultCode, data); } if (requestCode == REQUEST_BATTERY_OP) { - setNeverAskForBatteryOptimizationsAgain(); + // Never Ask For Battery Optimizations Again + ConversationsPlusPreferences.commitShowBatteryOptimization(false); } } } - private void setNeverAskForBatteryOptimizationsAgain() { - getPreferences().edit().putBoolean("show_battery_optimization", false).commit(); + private long getMaxHttpUploadSize(Conversation conversation) { + return conversation.getAccount().getXmppConnection().getFeatures().getMaxHttpUploadSize(); } private void openBatteryOptimizationDialogIfNeeded() { if (hasAccountWithoutPush() && isOptimizingBattery() - && getPreferences().getBoolean("show_battery_optimization", true)) { + && ConversationsPlusPreferences.showBatteryOptimization()) { AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(R.string.battery_optimizations_enabled); builder.setMessage(R.string.battery_optimizations_enabled_dialog); @@ -1462,7 +1461,7 @@ public class ConversationActivity extends XmppActivity builder.setOnDismissListener(new DialogInterface.OnDismissListener() { @Override public void onDismiss(DialogInterface dialog) { - setNeverAskForBatteryOptimizationsAgain(); + ConversationsPlusPreferences.commitShowBatteryOptimization(false); } }); } diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/ConversationFragment.java b/src/main/java/de/thedevstack/conversationsplus/ui/ConversationFragment.java index 7c5f6ffa..4ae88a5e 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ui/ConversationFragment.java +++ b/src/main/java/de/thedevstack/conversationsplus/ui/ConversationFragment.java @@ -313,10 +313,10 @@ public class ConversationFragment extends Fragment implements EditMessage.Keyboa public void setupIme() { if (activity == null) { return; - } else if (activity.usingEnterKey() && ConversationsPlusPreferences.enterIsSend()) { + } else if (ConversationsPlusPreferences.displayEnterKey() && ConversationsPlusPreferences.enterIsSend()) { mEditMessage.setInputType(mEditMessage.getInputType() & (~InputType.TYPE_TEXT_FLAG_MULTI_LINE)); mEditMessage.setInputType(mEditMessage.getInputType() & (~InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE)); - } else if (activity.usingEnterKey()) { + } else if (ConversationsPlusPreferences.displayEnterKey()) { mEditMessage.setInputType(mEditMessage.getInputType() | InputType.TYPE_TEXT_FLAG_MULTI_LINE); mEditMessage.setInputType(mEditMessage.getInputType() & (~InputType.TYPE_TEXT_VARIATION_SHORT_MESSAGE)); } else { diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/EditAccountActivity.java b/src/main/java/de/thedevstack/conversationsplus/ui/EditAccountActivity.java index 2b8e619f..1a868949 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ui/EditAccountActivity.java +++ b/src/main/java/de/thedevstack/conversationsplus/ui/EditAccountActivity.java @@ -39,6 +39,7 @@ import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import de.thedevstack.conversationsplus.ConversationsPlusColors; +import de.thedevstack.conversationsplus.ConversationsPlusPreferences; import de.thedevstack.conversationsplus.ui.listeners.ShowResourcesListDialogListener; import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.R; @@ -246,12 +247,11 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate mFetchingAvatar = true; AvatarService.getInstance().checkForAvatar(mAccount, mAvatarFetchCallback); } - } else { - updateSaveButton(); } if (mAccount != null) { updateAccountInformation(false); } + updateSaveButton(); } @Override @@ -522,8 +522,7 @@ public class EditAccountActivity extends XmppActivity implements OnAccountUpdate } } } - SharedPreferences preferences = getPreferences(); - this.mShowOptions = preferences.getBoolean("show_connection_options", false); + this.mShowOptions = ConversationsPlusPreferences.showConnectionOptions(); this.mNamePort.setVisibility(mShowOptions ? View.VISIBLE : View.GONE); } @@ -531,6 +530,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); @@ -625,6 +625,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/de/thedevstack/conversationsplus/ui/LogCatOutputActivity.java b/src/main/java/de/thedevstack/conversationsplus/ui/LogCatOutputActivity.java index d15b59c6..fe7968cb 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ui/LogCatOutputActivity.java +++ b/src/main/java/de/thedevstack/conversationsplus/ui/LogCatOutputActivity.java @@ -2,27 +2,55 @@ package de.thedevstack.conversationsplus.ui; import android.app.Activity; import android.os.Bundle; +import android.view.ContextMenu; +import android.view.MenuItem; +import android.view.View; +import android.widget.AdapterView; import android.widget.Button; import android.widget.ListView; import de.thedevstack.android.logcat.adapters.LogCatArrayAdapter; import de.thedevstack.android.logcat.tasks.ReadLogCatAsyncTask; -import de.thedevstack.android.logcat.ui.LogCatOutputCopyOnClickListener; import de.thedevstack.conversationsplus.R; +import de.thedevstack.conversationsplus.ui.listeners.LogCatOutputCopyOnClickListener; +import de.thedevstack.conversationsplus.utils.ClipboardUtil; /** - * Created by tzur on 07.10.2015. + * Activity to display the logcat output. */ public class LogCatOutputActivity extends Activity { + /** + * List adapter containing the logcat entries. + */ + private LogCatArrayAdapter logCatArrayAdapter; + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_logcatoutput); ListView lv = (ListView)findViewById(R.id.actLogInfoOutput); - LogCatArrayAdapter logCatOutputAdapter = new LogCatArrayAdapter(this, R.layout.list_item_logcatoutput); - lv.setAdapter(logCatOutputAdapter); - new ReadLogCatAsyncTask(logCatOutputAdapter).execute(); + this.logCatArrayAdapter = new LogCatArrayAdapter(this, R.layout.list_item_logcatoutput); + lv.setAdapter(this.logCatArrayAdapter); + new ReadLogCatAsyncTask(this.logCatArrayAdapter).execute(); Button copyButton = (Button) findViewById(R.id.actLogOutputCopyButton); - copyButton.setOnClickListener(new LogCatOutputCopyOnClickListener(this, logCatOutputAdapter, R.string.cplus_copied_to_clipboard, R.string.cplus_not_copied_to_clipboard_empty)); + copyButton.setOnClickListener(new LogCatOutputCopyOnClickListener(this.logCatArrayAdapter)); + registerForContextMenu(lv); + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, v, menuInfo); + menu.add(0, 123456789, 0, R.string.cplus_copy_item); + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + if (123456789 == item.getItemId()) { + AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) item.getMenuInfo(); + String itemText = this.logCatArrayAdapter.getItems().get(info.position); + ClipboardUtil.copyToClipboard(itemText); + return true; + } + return super.onContextItemSelected(item); } } diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/ShareWithActivity.java b/src/main/java/de/thedevstack/conversationsplus/ui/ShareWithActivity.java index 51fe92bb..fe3ed73f 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ui/ShareWithActivity.java +++ b/src/main/java/de/thedevstack/conversationsplus/ui/ShareWithActivity.java @@ -37,6 +37,8 @@ import de.thedevstack.conversationsplus.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); @@ -306,8 +309,7 @@ public class ShareWithActivity extends XmppActivity implements XmppConnectionSer }; } if (account.httpUploadAvailable() - && ((share.image && !neverCompressPictures()) - || conversation.getMode() == Conversation.MODE_MULTI + && (conversation.getMode() == Conversation.MODE_MULTI || FileUtils.allFilesUnderSize(this, share.uris, max)) && conversation.getNextEncryption() != Message.ENCRYPTION_OTR) { callback.onPresenceSelected(); @@ -315,8 +317,28 @@ public class ShareWithActivity extends XmppActivity implements XmppConnectionSer selectPresence(conversation, callback); } } else { - switchToConversation(conversation, this.share.text, true); - finish(); + 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/de/thedevstack/conversationsplus/ui/StartConversationActivity.java b/src/main/java/de/thedevstack/conversationsplus/ui/StartConversationActivity.java index bd3dd684..2dc8d1a5 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ui/StartConversationActivity.java +++ b/src/main/java/de/thedevstack/conversationsplus/ui/StartConversationActivity.java @@ -288,7 +288,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU if (!conversation.getMucOptions().online()) { xmppConnectionService.joinMuc(conversation); } - if (!bookmark.autojoin() && getPreferences().getBoolean("autojoin", true)) { + if (!bookmark.autojoin() && ConversationsPlusPreferences.autojoin()) { bookmark.setAutojoin(true); xmppConnectionService.pushBookmarks(bookmark.getAccount()); } @@ -438,7 +438,7 @@ public class StartConversationActivity extends XmppActivity implements OnRosterU jid.setError(getString(R.string.bookmark_already_exists)); } else { final Bookmark bookmark = new Bookmark(account, conferenceJid.toBareJid()); - bookmark.setAutojoin(getPreferences().getBoolean("autojoin", true)); + bookmark.setAutojoin(ConversationsPlusPreferences.autojoin()); String nick = conferenceJid.getResourcepart(); if (nick != null && !nick.isEmpty()) { bookmark.setNick(nick); diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/XmppActivity.java b/src/main/java/de/thedevstack/conversationsplus/ui/XmppActivity.java index f2ee20a0..726facac 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ui/XmppActivity.java +++ b/src/main/java/de/thedevstack/conversationsplus/ui/XmppActivity.java @@ -104,11 +104,8 @@ public abstract class XmppActivity extends Activity { public boolean xmppConnectionServiceBound = false; protected boolean registeredListeners = false; - protected boolean mUseSubject = true; - private DisplayMetrics metrics; protected int mTheme; - protected boolean mUsingEnterKey = false; private long mLastUiRefresh = 0; private Handler mRefreshUiHandler = new Handler(); @@ -353,8 +350,6 @@ public abstract class XmppActivity extends Activity { ExceptionHelper.init(getApplicationContext()); this.mTheme = findTheme(); setTheme(this.mTheme); - this.mUsingEnterKey = ConversationsPlusPreferences.displayEnterKey(); - mUseSubject = getPreferences().getBoolean("use_subject", true); final ActionBar ab = getActionBar(); if (ab!=null) { ab.setDisplayHomeAsUpEnabled(true); @@ -383,19 +378,6 @@ public abstract class XmppActivity extends Activity { } } - protected boolean usingEnterKey() { - return getPreferences().getBoolean("display_enter_key", false); - } - - protected SharedPreferences getPreferences() { - return PreferenceManager - .getDefaultSharedPreferences(getApplicationContext()); - } - - public boolean useSubjectToIdentifyConference() { - return mUseSubject; - } - public void switchToConversation(Conversation conversation) { switchToConversation(conversation, null, false); } @@ -957,10 +939,6 @@ public abstract class XmppActivity extends Activity { } } - protected boolean neverCompressPictures() { - return getPreferences().getString("picture_compression", "auto").equals("never"); - } - protected void unregisterNdefPushMessageCallback() { NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(this); if (nfcAdapter != null && nfcAdapter.isEnabled()) { diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/adapter/ConversationAdapter.java b/src/main/java/de/thedevstack/conversationsplus/ui/adapter/ConversationAdapter.java index 2b2d9298..e28774f2 100644 --- a/src/main/java/de/thedevstack/conversationsplus/ui/adapter/ConversationAdapter.java +++ b/src/main/java/de/thedevstack/conversationsplus/ui/adapter/ConversationAdapter.java @@ -21,6 +21,7 @@ import java.util.List; import java.util.concurrent.RejectedExecutionException; import de.thedevstack.conversationsplus.ConversationsPlusColors; +import de.thedevstack.conversationsplus.ConversationsPlusPreferences; import de.thedevstack.conversationsplus.ui.listeners.ShowResourcesListDialogListener; import de.tzur.conversations.Settings; import de.thedevstack.conversationsplus.R; @@ -58,7 +59,7 @@ public class ConversationAdapter extends ArrayAdapter<Conversation> { view.findViewById(R.id.conversationListRowFrame).setBackgroundColor(c); } TextView convName = (TextView) view.findViewById(R.id.conversation_name); - if (conversation.getMode() == Conversation.MODE_SINGLE || activity.useSubjectToIdentifyConference()) { + if (conversation.getMode() == Conversation.MODE_SINGLE || ConversationsPlusPreferences.useSubject()) { convName.setText(conversation.getName()); } else { convName.setText(conversation.getJid().toBareJid().toString()); diff --git a/src/main/java/de/thedevstack/conversationsplus/ui/listeners/LogCatOutputCopyOnClickListener.java b/src/main/java/de/thedevstack/conversationsplus/ui/listeners/LogCatOutputCopyOnClickListener.java new file mode 100644 index 00000000..f71c67db --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/ui/listeners/LogCatOutputCopyOnClickListener.java @@ -0,0 +1,41 @@ +package de.thedevstack.conversationsplus.ui.listeners; + +import android.view.View; + +import java.util.List; + +import de.thedevstack.android.logcat.Logging; +import de.thedevstack.android.logcat.adapters.LogCatArrayAdapter; +import de.thedevstack.conversationsplus.utils.ClipboardUtil; + +/** + * OnClickListener to copy logcat entries from LogCatArrayAdapter to clipboard. + */ +public class LogCatOutputCopyOnClickListener implements View.OnClickListener { + private final LogCatArrayAdapter logCatOutputAdapter; + + public LogCatOutputCopyOnClickListener(LogCatArrayAdapter logCatOutputAdapter) { + this.logCatOutputAdapter = logCatOutputAdapter; + } + + /** + * Copies the entries of LogCatArrayAdapter separated by a new line to the clipboard. + * + * @param v The view that was clicked. + */ + @Override + public void onClick(View v) { + Logging.d("copylogcat", "Start Copying log cat"); + List<String> items = this.logCatOutputAdapter.getItems(); + String textToCopy = null; + if (null != items && !items.isEmpty()) { + StringBuilder sb = new StringBuilder(); + for (String item : items) { + sb.append(item); + sb.append("\n"); + } + textToCopy = sb.toString(); + } + ClipboardUtil.copyToClipboard("c+logcat", textToCopy); + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/AvatarUtil.java b/src/main/java/de/thedevstack/conversationsplus/utils/AvatarUtil.java index 206ee668..9f6071bb 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; @@ -59,6 +61,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/de/thedevstack/conversationsplus/utils/ClipboardUtil.java b/src/main/java/de/thedevstack/conversationsplus/utils/ClipboardUtil.java new file mode 100644 index 00000000..1ef7cba9 --- /dev/null +++ b/src/main/java/de/thedevstack/conversationsplus/utils/ClipboardUtil.java @@ -0,0 +1,47 @@ +package de.thedevstack.conversationsplus.utils; + +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.widget.Toast; + +import de.thedevstack.conversationsplus.ConversationsPlusApplication; +import de.thedevstack.conversationsplus.R; + +/** + * Util class to work with the Clipboard. + */ +public final class ClipboardUtil { + private static final String CLIPBOARD_LABEL = "c+clipboard"; + + /** + * Copies a text to the clipboard. + * @param clipboardLabel the label to show to a user to allow identifying the text in clipboard. + * @param text the text to copy + */ + public static void copyToClipboard(String clipboardLabel, String text) { + Context context = ConversationsPlusApplication.getAppContext(); + if (null != text && !text.isEmpty()) { + String label = (null == clipboardLabel) ? CLIPBOARD_LABEL : clipboardLabel; + ClipboardManager clipboard = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); + ClipData clip = ClipData.newPlainText(label, text); + clipboard.setPrimaryClip(clip); + Toast.makeText(context, R.string.cplus_copied_to_clipboard, Toast.LENGTH_LONG).show(); + } else { + Toast.makeText(context, R.string.cplus_not_copied_to_clipboard_empty, Toast.LENGTH_LONG).show(); + } + + } + + /** + * Copies a text to the clipboard. + * @param text the text to copy + */ + public static void copyToClipboard(String text) { + copyToClipboard(CLIPBOARD_LABEL, text); + } + + private ClipboardUtil() { + // helper class - avoid instantiation + } +} diff --git a/src/main/java/de/thedevstack/conversationsplus/utils/Xmlns.java b/src/main/java/de/thedevstack/conversationsplus/utils/Xmlns.java index f937e35f..35adf6ec 100644 --- a/src/main/java/de/thedevstack/conversationsplus/utils/Xmlns.java +++ b/src/main/java/de/thedevstack/conversationsplus/utils/Xmlns.java @@ -7,5 +7,5 @@ public final class Xmlns { public static final String ROSTER = "jabber:iq:roster"; public static final String REGISTER = "jabber:iq:register"; public static final String BYTE_STREAMS = "http://jabber.org/protocol/bytestreams"; - public static final String HTTP_UPLOAD = Config.LEGACY_NAMESPACE_HTTP_UPLOAD ? "eu:siacs:conversations:http:upload" : "urn:xmpp:http:upload"; + public static final String HTTP_UPLOAD = "urn:xmpp:http:upload"; } diff --git a/src/main/java/de/thedevstack/conversationsplus/xmpp/XmppConnection.java b/src/main/java/de/thedevstack/conversationsplus/xmpp/XmppConnection.java index 58352d4f..a89f308f 100644 --- a/src/main/java/de/thedevstack/conversationsplus/xmpp/XmppConnection.java +++ b/src/main/java/de/thedevstack/conversationsplus/xmpp/XmppConnection.java @@ -37,6 +37,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; @@ -48,6 +50,7 @@ import javax.net.ssl.X509TrustManager; import de.duenndns.ssl.MemorizingTrustManager; import de.thedevstack.android.logcat.Logging; +import de.thedevstack.conversationsplus.ConversationsPlusPreferences; import de.thedevstack.conversationsplus.dto.SrvRecord; import de.thedevstack.conversationsplus.Config; import de.thedevstack.conversationsplus.crypto.XmppDomainVerifier; @@ -113,7 +116,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; @@ -245,7 +250,7 @@ public class XmppConnection implements Runnable { tagReader = new XmlReader(wakeLock); tagWriter = new TagWriter(); this.changeStatus(Account.State.CONNECTING); - final boolean extended = mXmppConnectionService.showExtendedConnectionOptions(); + final boolean extended = ConversationsPlusPreferences.showConnectionOptions(); if (extended && account.getHostname() != null && !account.getHostname().isEmpty()) { socket = new Socket(); try { @@ -525,7 +530,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) { @@ -711,42 +715,7 @@ public class XmppConnection implements Runnable { } else if (this.streamFeatures.hasChild("mechanisms") && shouldAuthenticate && (features.encryptionEnabled || Config.ALLOW_NON_TLS_CONNECTIONS)) { - final List<String> mechanisms = extractMechanisms(streamFeatures - .findChild("mechanisms")); - final Element auth = new Element("auth"); - auth.setAttribute("xmlns", "urn:ietf:params:xml:ns:xmpp-sasl"); - if (mechanisms.contains("EXTERNAL") && account.getPrivateKeyAlias() != null) { - saslMechanism = new External(tagWriter, account, mXmppConnectionService.getRNG()); - } else if (mechanisms.contains("SCRAM-SHA-1")) { - saslMechanism = new ScramSha1(tagWriter, account, mXmppConnectionService.getRNG()); - } else if (mechanisms.contains("PLAIN")) { - saslMechanism = new Plain(tagWriter, account); - } else if (mechanisms.contains("DIGEST-MD5")) { - saslMechanism = new DigestMd5(tagWriter, account, mXmppConnectionService.getRNG()); - } - if (saslMechanism != null) { - final JSONObject keys = account.getKeys(); - try { - if (keys.has(Account.PINNED_MECHANISM_KEY) && - keys.getInt(Account.PINNED_MECHANISM_KEY) > saslMechanism.getPriority()) { - Logging.e(Config.LOGTAG, "Auth failed. Authentication mechanism " + saslMechanism.getMechanism() + - " has lower priority (" + String.valueOf(saslMechanism.getPriority()) + - ") than pinned priority (" + keys.getInt(Account.PINNED_MECHANISM_KEY) + - "). Possible downgrade attack?"); - throw new SecurityException(); - } - } catch (final JSONException e) { - Logging.d(Config.LOGTAG, "Parse error while checking pinned auth mechanism"); - } - Logging.d(Config.LOGTAG, account.getJid().toString() + ": Authenticating with " + saslMechanism.getMechanism()); - auth.setAttribute("mechanism", saslMechanism.getMechanism()); - if (!saslMechanism.getClientFirstMessage().isEmpty()) { - auth.setContent(saslMechanism.getClientFirstMessage()); - } - tagWriter.writeElement(auth); - } else { - throw new IncompatibleServerException(); - } + 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); @@ -762,6 +731,45 @@ public class XmppConnection implements Runnable { } } + private void authenticate() throws IOException { + final List<String> mechanisms = extractMechanisms(streamFeatures + .findChild("mechanisms")); + final Element auth = new Element("auth"); + auth.setAttribute("xmlns", "urn:ietf:params:xml:ns:xmpp-sasl"); + if (mechanisms.contains("EXTERNAL") && account.getPrivateKeyAlias() != null) { + saslMechanism = new External(tagWriter, account, mXmppConnectionService.getRNG()); + } else if (mechanisms.contains("SCRAM-SHA-1")) { + saslMechanism = new ScramSha1(tagWriter, account, mXmppConnectionService.getRNG()); + } else if (mechanisms.contains("PLAIN")) { + saslMechanism = new Plain(tagWriter, account); + } else if (mechanisms.contains("DIGEST-MD5")) { + saslMechanism = new DigestMd5(tagWriter, account, mXmppConnectionService.getRNG()); + } + if (saslMechanism != null) { + final JSONObject keys = account.getKeys(); + try { + if (keys.has(Account.PINNED_MECHANISM_KEY) && + keys.getInt(Account.PINNED_MECHANISM_KEY) > saslMechanism.getPriority()) { + Logging.e(Config.LOGTAG, "Auth failed. Authentication mechanism " + saslMechanism.getMechanism() + + " has lower priority (" + String.valueOf(saslMechanism.getPriority()) + + ") than pinned priority (" + keys.getInt(Account.PINNED_MECHANISM_KEY) + + "). Possible downgrade attack?"); + throw new SecurityException(); + } + } catch (final JSONException e) { + Logging.d(Config.LOGTAG, "Parse error while checking pinned auth mechanism"); + } + Logging.d(Config.LOGTAG, account.getJid().toString() + ": Authenticating with " + saslMechanism.getMechanism()); + auth.setAttribute("mechanism", saslMechanism.getMechanism()); + if (!saslMechanism.getClientFirstMessage().isEmpty()) { + auth.setContent(saslMechanism.getClientFirstMessage()); + } + tagWriter.writeElement(auth); + } else { + throw new IncompatibleServerException(); + } + } + private List<String> extractMechanisms(final Element stream) { final ArrayList<String> mechanisms = new ArrayList<>(stream .getChildren().size()); @@ -979,11 +987,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"); @@ -998,13 +1007,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"); @@ -1048,14 +1059,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(); } } } @@ -1065,6 +1072,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(); @@ -1082,7 +1097,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) { @@ -1099,8 +1114,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() { @@ -1481,13 +1505,15 @@ 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()); - return info != null && info.hasIdentity("pubsub", "pep"); - } + 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"); } } |